Objectifs du cours


Le document possédant l’extension “.Rmd” est au format markdown (comme wikipedia) et peut être annoté en tout temps.

N’hésitez pas à prendre vos notes directement dans ce document.


Quelques ressources utiles


Computational Historical Thinking With Applications in R de Lincoln A. Mullen (incomplet)

http://dh-r.lincolnmullen.com/

Le syllabus de son cours.

http://lincolnmullen.com/courses/clio2.2018/

The Historian’s Macroscope: Big Digital History de Shawn Graham, Ian Milligan et Scott Weingart

http://www.themacroscope.org/?page_id=707

Le livre a été publié depuis, voir http://www.themacroscope.org/2.0/


Une ressource en français.

Introduction à R et au tidyverse de Julien Barnier

https://juba.github.io/tidyverse


R for Data Science de Garrett Grolemund & Hadley Wickham

http://r4ds.had.co.nz/

An Introduction to Statistical and Data Sciences via R de Chester Ismay & Albert Y. Kim

http://moderndive.com/

Excel vs R: A Brief Introduction to R

https://www.jessesadler.com/post/excel-vs-r/

The Programming Historian

https://programminghistorian.org/


Les cheatsheets de RStudio

https://www.rstudio.com/resources/cheatsheets/


Qu’est-ce que R ?

R est un langage de programmation à l’origine prévu pour faire de l’analyse statistique.

R est disponible sous licence libre. Il est basé sur le langage propriétaire S.

Son usage s’est répandu bien au-delà des statistiques.


Mais R désigne également une interface de programmation que l’on nomme R GUI, “GUI” signifiant “Graphical User Interface”.

R GUI est un logiciel libre.


Qu’est-ce que RStudio ?

RStudio est un environnement permettant de programmer en R, c’est-à-dire un peu plus qu’une simple interface graphique.


En plus d’afficher une console (là où le code est exécuté, généralement dans la fenêtre en bas à gauche) et une fenêtre de script (là où l’on rédige le code que l’on veut réutiliser, généralement en haut à gauche), RStudio permet par exemple de conserver bien en vue la liste des variables enregistrées en mémoire (en haut à droite), ou d’accéder aisément à des fichiers d’aide (en bas à droite).

RStudio permet également d’utiliser un notebook comme celui-ci.


Comment fonctionnent les notebooks ?

Ce document que vous avez ouvert dans RStudio ou dans un navigateur web est un notebook.


C’est un format de document permettant de présenter du texte, du code, et les résultats de l’exécution du code.


Dans la recherche scientifique, ce format permet de répliquer une expérience.

La démarche de partager un notebook en même temps qu’un article scientifique devrait à l’avenir devenir un standard.


Le système de notebooks le plus répandu est Jupyter, qui fonctionne avec les langages de programmation Julia, Python et R, mais permet aussi de gérer d’autres langages de programmation.


Ici, nous utilisons des notebooks au format prévu par RStudio.

Un notebook peut se décliner en plusieurs formats.


Le code apparaît systématiquement dans une case sur fond gris.

Le résultat du code apparaît ensuite dans une case sur fond blanc ou gris selon les réglages.

for (i in 1:3) print(LETTERS[i])
[1] "A"
[1] "B"
[1] "C"

En ouvrant le notebook dans RStudio, vous aurez la possibilité de modifier le code et de le réexécuter.


Par exemple, dans le code ci-dessous, nous sauvons la valeur “2” dans la variable “a” et la valeur “e” dans la variable “b”. Puis nous élevons a à la puissance b, c’est-à-dire 2 à la puissance 3.

Le résultat s’affiche en-dessous.

a <- 2
b <- 3
a^b
[1] 8

Exercice 1.1


Obtenir de l’aide…

… quand on connaît le nom de la fonction.

help(exp)                     ## La page d'aide d'une variable
?exp                          ## Une autre syntaxe pour le même résultat

La touche de tabulation est utile dans le cas où les premières lettres d’une fonction sont connues.


… quand on ne connaît pas le nom de la fonction mais seulement celui de la méthode.

help.search("linear model")   ## Chercher dans les pages d'aide
??"linear model"              ## Une autre syntaxe pour le même résultat

Dans ce notebook comme dans les scripts que vous trouverez en ligne, les fonctions peuvent sembler sortir d’un chapeau comme si de rien n’était.

Ne pas se faire d’illusions:


C’est pour cette raison qu’il ne faut jamais hésiter à faire une recherche


Autres ressources en ligne (veille)


Exercice 1.2


La cheatsheet pour l’importation et l’exportation de données.

https://github.com/rstudio/cheatsheets/raw/master/data-import.pdf


Les fonctions

Une fonction prédéfinie.

y = sqrt(4)    ## Fonction racine carrée
y
[1] 2

Toutes les fonctions du package base.

help(package="base")

Une fonction construite pour l’occasion.

f <- function(x) {2*x}
f(17)
[1] 34
f(pi)
[1] 6.283185

Les classes


Connaître la classe d’un objet.

z <- "hello"
class(z)                     ## Le type d'une variable
[1] "character"

La fonction str permet d’obtenir des informations plus complètes.

str(z)
 chr "hello"

vec <- c(3,5,3,7)
str(vec)
 num [1:4] 3 5 3 7

f <- function(x) {2*x}
str(f)
function (x)  
 - attr(*, "srcref")=Class 'srcref'  atomic [1:8] 1 6 1 22 6 22 1 1
  .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x10f12cc78> 

a <- f(17)
str(a)
 num 34

Chaînes de caractères

Les chaînes de caractères sont des assemblages de symboles.

b <- "unil"
b
[1] "unil"

Il existe de nombreuses fonctions pour les manipuler.

Par exemple pour connaître leur longueur.

nchar(b)
[1] 4

Pour manipuler du texte de manière avancée dans R (ou dans n’importe quel autre langage de programmation), il peut être nécessaire d’apprendre à manipuler des expressions régulières.


Deux tutoriaux pour R.


La cheatsheet, indispensable.

https://github.com/rstudio/cheatsheets/raw/master/strings.pdf


Pour tester des expressions régulières :

https://regex101.com/


Quelques ressources ludiques pour s’entraîner…


Les éléments logiques

Mais aussi…

“NA” signifie “Not available”.

“NaN” signifie “Not a Number”.


Les opérateurs de comparaison

1 == 1
[1] TRUE
1 == 2
[1] FALSE

1 != 1 
[1] FALSE
1 != 2
[1] TRUE

1 < 1
[1] FALSE
1 <= 1
[1] TRUE

Les vecteurs

x <- c(5,4,5,6,7,8) 
x
[1] 5 4 5 6 7 8
x_char = c("a","b","c") 
x_char
[1] "a" "b" "c"

Accéder directement à un élément d’un vecteur

x
[1] 5 4 5 6 7 8
x[2]
[1] 4
x[c(2,4)]
[1] 4 6

x
[1] 5 4 5 6 7 8
x[c(-2,-4)]
[1] 5 5 7 8

Manipuler des vecteurs

x[6] = 10
x
[1]  5  4  5  6  7 10

a = c(3,4,5,6)
a[c(2,3)] = 0
a
[1] 3 0 0 6

a[c(2,3)] = c(8,7)
a
[1] 3 8 7 6

c(1,2,3) + c(3,4,5)
[1] 4 6 8

4 * c(1,2,3)
[1]  4  8 12
c(2,5) < 4
[1]  TRUE FALSE

Attention lorsque deux éléments ne sont pas de même taille !

c(1,2) + c(3,4,5,6)
[1] 4 6 6 8

Attention lorsque l’un n’est pas multiple de l’autre !

c(1,2) + c(2,3,4)
longer object length is not a multiple of shorter object length
[1] 3 5 5

Comment connaître le type d’une variable ?

a <- 1
b <- 1:5
class(a)
[1] "numeric"
class(b)
[1] "integer"

c <- "hello"
d <- 1 > 2
class(c)
[1] "character"
class(d)
[1] "logical"

Quelle est la “taille” de l’objet ?

length(a)
[1] 1
length(b)
[1] 5
length(c)
[1] 1
length(d)
[1] 1

Manipulations de l’environnement

ls()    ## Les variables sauvées dans l'environnement de travail
 [1] "a"                 "aie"               "b"                 "Best_contentStop"  "Best_formStop"    
 [6] "c"                 "d"                 "dist"              "f"                 "g"                
[11] "i"                 "m1"                "m2"                "mp"                "multiplot"        
[16] "ouch"              "p1"                "p2"                "p3"                "p4"               
[21] "retards"           "tableau"           "tablreau"          "v1"                "v2"               
[26] "vec"               "Worst_contentStop" "Worst_formStop"    "x"                 "x_char"           
[31] "y"                 "z"                
rm(y)   ## On retire la variable "y"
ls()
 [1] "a"                 "aie"               "b"                 "Best_contentStop"  "Best_formStop"    
 [6] "c"                 "d"                 "dist"              "f"                 "g"                
[11] "i"                 "m1"                "m2"                "mp"                "multiplot"        
[16] "ouch"              "p1"                "p2"                "p3"                "p4"               
[21] "retards"           "tableau"           "tablreau"          "v1"                "v2"               
[26] "vec"               "Worst_contentStop" "Worst_formStop"    "x"                 "x_char"           
[31] "z"                

Exercice 2

  • Créez un vecteur avec les cinq valeurs suivantes: 5, 10, 15, 20 et 25.
  • Cherchez quelle fonction permet de calculer la moyenne («mean» en anglais) d’une distribution.
  • Calculez-la :-)

Le package readr

À partir de là, nous allons utiliser le package readr pour lire et écrire des informations textuelles.

Le code suivant vérifie si le package est installé sur votre machine et l’installe le cas échéant. Puis il le charge.

if (!require(readr)) install.packages("readr")
library(readr)

  • Les packages sont des ensembles de fonctions que l’on ajoute aux fonctions déjà disponibles.
  • Les fonctions d’un package ont en général été regroupées autour d’une thématique commune.
  • Dans le cas de readr: l’importation et l’exportation de données textuelles.

Pour l’exercice, nous allons utiliser un jeu de données avec une licence ouverte, trouvé sur le site data.gouv.fr

https://www.data.gouv.fr/fr/datasets/indices-mensuels-de-retard-des-bus/#_


csv (comma separated values)

Tout d’abord, il faut sauver le fichier dans le dossier de travail (“ENS-DH-R” dans notre cas).

Puis, pour l’importer, il ne faut pas oublier les guillemets autour du nom du fichier.

retards <- read_csv2("indices-mensuels-de-retard-des-bus.csv")
Using ',' as decimal and '.' as grouping mark. Use read_delim() for more control.
Parsed with column specification:
cols(
  .default = col_number(),
  Mois = col_character(),
  `Citadine Halluin` = col_character(),
  Z1 = col_character()
)
See spec(...) for full column specifications.

Il est important de lire les commentaires (souvent indiqués en rouge, comme les erreurs… malin).

Un aperçu de ce que nous avons importé.

retards

Un autre type d’aperçu.

str(retards)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   11 obs. of  60 variables:
 $ Mois                : chr  "08. Aout" "09. Septembre" "10. Octobre" "11. Novembre" ...
 $ Citadine Tourcoing  : num  106 109 102 109 104 141 126 92 94 141 ...
 $ Citadine Roubaix    : num  65 93 93 117 123 81 142 89 11 163 ...
 $ Citadine Lille      : num  91 148 95 109 83 76 95 78 73 66 ...
 $ MWR                 : num  211 213 149 78 72 213 103 90 170 87 ...
 $ COR                 : num  113 171 176 201 138 173 121 146 164 129 ...
 $ Citadine Armentières: num  29 20 18 20 41 67 135 44 48 36 ...
 $ Citadine Halluin    : chr  "1.0" "4.3" "6.8" "1.9" ...
 $ L1                  : num  121 162 120 119 106 158 104 98 110 97 ...
 $ L2                  : num  40 78 62 55 71 55 62 54 65 57 ...
 $ L3                  : num  96 121 50 64 43 87 48 58 54 65 ...
 $ L4                  : num  143 208 141 181 154 171 129 134 162 165 ...
 $ L90                 : num  51 64 60 66 56 99 42 48 64 56 ...
 $ L91                 : num  64 111 87 130 79 119 93 87 114 76 ...
 $ Z1                  : chr  "0.8" "2.9" "1.8" "1.9" ...
 $ Z2                  : num  30 43 48 65 15 24 17 20 18 22 ...
 $ Z3                  : num  13 24 47 43 38 31 34 17 47 55 ...
 $ Z4                  : num  94 34 22 39 19 148 15 10 20 29 ...
 $ 10                  : num  89 106 114 134 109 123 105 118 108 115 ...
 $ 11                  : num  58 105 113 119 75 78 70 77 114 98 ...
 $ 12                  : num  137 218 206 157 129 181 120 136 169 122 ...
 $ 13                  : num  50 93 76 114 89 91 75 80 91 90 ...
 $ 14                  : num  193 232 208 222 147 243 156 143 141 127 ...
 $ 15                  : num  77 129 131 163 111 61 125 119 118 132 ...
 $ 16                  : num  95 139 146 162 119 91 110 91 131 157 ...
 $ 17                  : num  133 119 88 85 77 130 56 71 101 78 ...
 $ 18                  : num  167 195 193 190 177 139 131 153 176 200 ...
 $ 30                  : num  115 167 132 142 100 107 109 81 122 106 ...
 $ 32                  : num  84 98 94 89 101 149 76 84 99 92 ...
 $ 33                  : num  58 110 105 106 146 71 86 89 94 123 ...
 $ 35                  : num  56 148 128 126 118 91 139 99 131 124 ...
 $ 36                  : num  62 66 46 57 40 70 46 50 48 68 ...
 $ 37                  : num  125 153 76 71 71 116 61 76 74 77 ...
 $ 50                  : num  72 101 122 153 94 106 77 94 91 91 ...
 $ 51                  : num  43 41 50 45 33 33 43 37 32 34 ...
 $ 52                  : num  42 79 130 114 67 69 84 76 70 71 ...
 $ 53                  : num  99 126 85 93 72 89 97 61 93 89 ...
 $ 54                  : num  118 117 100 140 83 108 87 67 98 88 ...
 $ 55                  : num  95 115 130 155 98 112 99 93 102 94 ...
 $ 57                  : num  80 129 101 132 126 93 94 91 126 104 ...
 $ 58                  : num  73 156 116 117 92 85 96 78 86 114 ...
 $ 59                  : num  26 47 50 62 38 37 37 45 40 55 ...
 $ 56                  : num  33 66 76 108 74 88 101 83 71 77 ...
 $ 61                  : num  54 57 77 95 24 54 55 27 34 66 ...
 $ 63                  : num  60 67 113 85 53 99 97 70 51 63 ...
 $ 64                  : num  93 63 68 70 43 112 37 35 51 41 ...
 $ 65                  : num  13 50 63 55 15 29 20 16 64 48 ...
 $ 66                  : num  44 44 47 39 56 50 50 47 41 44 ...
 $ 67                  : num  39 61 36 40 45 27 34 33 37 22 ...
 $ 75                  : num  54 66 43 73 22 20 26 36 84 82 ...
 $ 76                  : num  46 47 39 44 21 21 14 22 24 24 ...
 $ 78                  : num  NA 87 67 68 37 67 21 21 49 60 ...
 $ 79                  : num  41 50 60 76 53 59 83 45 46 78 ...
 $ 80                  : num  53 61 44 75 64 58 61 61 60 63 ...
 $ 81                  : num  53 78 64 85 45 55 50 57 30 36 ...
 $ 82                  : num  80 90 93 96 81 110 92 70 93 72 ...
 $ 84                  : num  57 59 51 72 67 59 74 57 72 78 ...
 $ 86                  : num  72 108 87 78 58 112 52 63 101 60 ...
 $ 87                  : num  37 64 55 64 70 32 62 41 64 82 ...
 $ 88                  : num  29 49 65 88 44 62 39 68 59 43 ...
 - attr(*, "spec")=List of 2
  ..$ cols   :List of 60
  .. ..$ Mois                : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ Citadine Tourcoing  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ Citadine Roubaix    : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ Citadine Lille      : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ MWR                 : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ COR                 : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ Citadine Armentières: list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ Citadine Halluin    : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ L1                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ L2                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ L3                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ L4                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ L90                 : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ L91                 : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ Z1                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ Z2                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ Z3                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ Z4                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 10                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 11                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 12                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 13                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 14                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 15                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 16                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 17                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 18                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 30                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 32                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 33                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 35                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 36                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 37                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 50                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 51                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 52                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 53                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 54                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 55                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 57                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 58                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 59                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 56                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 61                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 63                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 64                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 65                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 66                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 67                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 75                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 76                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 78                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 79                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 80                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 81                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 82                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 84                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 86                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 87                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  .. ..$ 88                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_number" "collector"
  ..$ default: list()
  .. ..- attr(*, "class")= chr  "collector_guess" "collector"
  ..- attr(*, "class")= chr "col_spec"

Pour consulter l’objet importé - dans ce cas un tableau de données - vous pouvez vous rendre dans la fenêtre en haut à droite de RStudio puis dans l’onglet “Environment”. Vous trouverez le tableau sous “Data”. En double-cliquant dessus, il s’ouvrira dans cette fenêtre et vous pourrez vérifier que le tableau a bien été importé.


Pour sauver un fichier au format csv, il faut utiliser la fonction write_csv

## write_csv(retards, "retards.csv")

xls (Microsoft Excel/libreOffice/Google Spreadsheets)

  • On préfère en général sauver les fichiers au format csv ou json car ils sont ainsi beaucoup plus légers et ne transportent pas l’encodage parfois lourd d’un fichier xls.
  • Néanmoins, ce n’est pas toujours possible d’y échapper lors d’une importation.
  • Si possible, exporter depuis Excel ou LibreOffice vos données au format csv (pour “comma separated values”)
  • Sinon…

readxl est un package qui permet d’importer (et d’exporter) des fichiers au format xls.

if (!require(readxl)) install.packages("readxl")
library(readxl)

Prenons un fichier au hasard de l’OFS : “Comportement de la population en matière de transport, chiffres clés - agglomération de Lausanne (définition 2000)”

https://www.bfs.admin.ch/bfs/fr/home/statistiques/catalogues-banques-donnees/tableaux.assetdetail.2082350.html

Il est présent dans le dossier.


La fonction read_excel est ce qu’il nous faut.

aie <- read_excel("su-f-11.04.03-MZ-2015-A2_5586_Def2000.xls")
aie

Toutes les entrées sont considérées comme des chaînes de caractères.

str(aie)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   39 obs. of  55 variables:
 $ Comportement de la population en matière de transport, chiffres clés 2015 - agglomération de Lausanne (définition 2000): chr  "Disponibilité d'une voiture, possession de permis et d'abonnement, taux de mobilité, distance journalière, durée moyenne des dé"| __truncated__ NA NA NA ...
 $ X__1                                                                                                                   : chr  NA "Outils de transport" "Disponibilité d'une voiture" "[%]" ...
 $ X__2                                                                                                                   : chr  NA NA NA "+/-" ...
 $ X__3                                                                                                                   : chr  NA NA "Possession du permis de conduire" "[%]" ...
 $ X__4                                                                                                                   : chr  NA NA NA "+/-" ...
 $ X__5                                                                                                                   : chr  NA NA "Possession d'abonnements des TP" "[%]" ...
 $ X__6                                                                                                                   : chr  NA NA NA "+/-" ...
 $ X__7                                                                                                                   : chr  NA "Taux de mobilité" "Personnes mobiles le jour de référence (en déplacement)" "[%]" ...
 $ X__8                                                                                                                   : chr  NA NA NA "+/-" ...
 $ X__9                                                                                                                   : chr  NA "Distance journalière moyenne" "Total" "[km]" ...
 $ X__10                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__11                                                                                                                  : chr  NA NA "Mobilité douce" "[km]" ...
 $ X__12                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__13                                                                                                                  : chr  NA NA "Transport individuel motorisé" "[km]" ...
 $ X__14                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__15                                                                                                                  : chr  NA NA "Transports publics" "[km]" ...
 $ X__16                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__17                                                                                                                  : chr  NA NA "Travail" "[km]" ...
 $ X__18                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__19                                                                                                                  : chr  NA NA "Loisirs" "[km]" ...
 $ X__20                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__21                                                                                                                  : chr  NA "Durée moyenne des déplacements (sans les temps d'attente et de correspondance)" "Total" "[min.]" ...
 $ X__22                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__23                                                                                                                  : chr  NA NA "Mobilité douce" "[min.]" ...
 $ X__24                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__25                                                                                                                  : chr  NA NA "Transport individuel motorisé" "[min.]" ...
 $ X__26                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__27                                                                                                                  : chr  NA NA "Transports publics" "[min.]" ...
 $ X__28                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__29                                                                                                                  : chr  NA "Durée moyenne des déplacements (avec les temps d'attente et de correspondance)" "Total" "[min.]" ...
 $ X__30                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__31                                                                                                                  : chr  NA NA "Travail" "[min.]" ...
 $ X__32                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__33                                                                                                                  : chr  NA NA "Loisirs" "[min.]" ...
 $ X__34                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__35                                                                                                                  : chr  NA "Nombre d'étapes par jour" "Total" "[nombre]" ...
 $ X__36                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__37                                                                                                                  : chr  NA NA "Mobilité douce" "[nombre]" ...
 $ X__38                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__39                                                                                                                  : chr  NA NA "Transport individuel motorisé" "[nombre]" ...
 $ X__40                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__41                                                                                                                  : chr  NA NA "Transports publics" "[nombre]" ...
 $ X__42                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__43                                                                                                                  : chr  NA NA "Travail" "[nombre]" ...
 $ X__44                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__45                                                                                                                  : chr  NA NA "Loisirs" "[nombre]" ...
 $ X__46                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__47                                                                                                                  : chr  NA "Nombre de déplacements par jour" "Total" "[nombre]" ...
 $ X__48                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__49                                                                                                                  : chr  NA NA "Travail" "[nombre]" ...
 $ X__50                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__51                                                                                                                  : chr  NA NA "Loisirs" "[nombre]" ...
 $ X__52                                                                                                                  : chr  NA NA NA "+/-" ...
 $ X__53                                                                                                                  : chr  NA "Nombre de boucles par jour" "Total" "[nombre]" ...
 $ X__54                                                                                                                  : chr  NA NA NA "+/-" ...

Le résultat n’est pas directement exploitable, et ce sera souvent le cas au moment de récupérer des fichiers publics, souvent structurés pour la lecture et pas pour une exploitation automatique.


Important : les fonctions utilisées traditionnellement pour importer des tableaux (par ex. read.table) importent par défaut les variables au format “factor”, un concept spécifique à R et aux langages prévus pour faire des statistiques. Ce format s’emploie principalement lorsqu’une variable est catégorielle ordinale, c’est-à-dire que les valeurs qu’elle peut prendre sont parmi un ensemble de “mots” (catégorielle) et qu’on peut les classer (ordinale), par exemple l’ensemble : “très mauvais”, “mauvais”, “bon”, “très bon”.

Pour obtenir ce résultat, il faut utiliser (quand elle est disponible) l’option stringsAsFactors = TRUE qui est généralement vraie («TRUE») par défaut.


Le même genre de technique peut être utilisé pour lire des fichiers aux formats suivants.

but fonction
pur texte read_file, read_lines
png readPNG
spss package haven
json package jsonlite
xml package xml2

Le traitement de données

Les data frames sont un des formats les plus importants et les plus populaires de R.

Il s’agit d’un tableau de données, qu’il ne faut pas confondre avec les formats matrix et table.


Tous les éléments d’une matrice sont du même type.

m1 <- matrix(c(1, 2, 3, 4), ncol = 2)
m1
     [,1] [,2]
[1,]    1    3
[2,]    2    4

Ici ce sont tous des nombres.

str(m1)
 num [1:2, 1:2] 1 2 3 4

Mais si on glisse des lettres dans la liste…

m2 <- matrix(c(1, "b", "c", 4), ncol = 2)
m2
     [,1] [,2]
[1,] "1"  "c" 
[2,] "b"  "4" 

Les éléments numériques sont devenus des chaînes de caractères.

str(m2)
 chr [1:2, 1:2] "1" "b" "c" "4"

Il se passe quelque chose d’équivalent avec les vecteurs.

v1 <- c(1, 2, 3)
v1
[1] 1 2 3

v2 <- c(1, "b", 3)
v2
[1] "1" "b" "3"

En les comparant…

str(v1)
 num [1:3] 1 2 3
str(v2)
 chr [1:3] "1" "b" "3"

Le format table quand à lui est utilisé pour donner des tables de contingence.

Dans cet exemple, on tire 20 fois un dé 6.

dist <- round(runif(20, min = 1, max = 6))
dist
 [1] 1 4 2 5 5 2 1 6 1 6 3 3 3 3 6 4 1 6 4 2

On regroupe les résultats les résultats.

table(dist)
dist
1 2 3 4 5 6 
4 3 4 3 2 4 

Un data frame est un tableau avec des observations en ligne et des variables en colonne. Les variables peuvent être de tout type (numériques, ordinales, catégorielles, dates, etc.).


Manipuler un data frame

On reprend le tableau de données des retards de bus vu précédemment.

Un aperçu rapide.

head(retards)

Comment accéder à une entrée (ligne).

retards[1,]

Comment accéder à une variable.

retards[,1]    ## la première variable

De manière équivalente, si on a la connaissance de ladite variable.

retards$Mois
 [1] "08. Aout"      "09. Septembre" "10. Octobre"   "11. Novembre"  "03. Mars"      "07. Juillet"   "02. Février"  
 [8] "04. Avril"     "05. Mai"       "01. Janvier"   "06. Juin"     

Comment réarranger le tableau en fonction des mois de l’année ?

retards <- retards[order(retards$Mois),]
retards

La fonction order appliquée à retards$Mois permet de classer les mois dans l’ordre donné par les chaîne de caractère 01 à 11.

Ensuite, placé avant la virgule, order(retards$Mois) signifie que nous réarrangeons l’ordre des lignes du tableau. L’effet s’applique sur les lignes entières, et pas uniquement sur les cases de la colonne concernée.


Nous allons voir dans la prochaine partie comment faire des visualisations à partir d’un tableau de données.


Pour aller plus loin, un tutorial est disponible ici

http://www.cookbook-r.com/Manipulating_data/

Un autre ici

http://tutorials.iq.harvard.edu/R/Rgraphics/Rgraphics.html

et des exercices ici

https://www.datacamp.com/community/tutorials/15-easy-solutions-data-frame-problems-r


Pour apprendre à manipuler correctement un ou plusieurs tableaux de données, créer et transformer des variables, sélectionner des sous-ensembles, le package dplyr est chaudement recommandé.

https://cran.r-project.org/web/packages/dplyr/vignettes/dplyr.html


Une cheatsheet est également disponible.

https://github.com/rstudio/cheatsheets/raw/master/data-transformation.pdf


La visualisation de données

ggplot2 met en pratique la grammaire des graphiques, une théorie de Leland Wilkinson (1999).

http://www.springer.com/us/book/9780387245447


Setup.

if (!require(ggplot2)) install.packages("ggplot2")
Loading required package: ggplot2
library(ggplot2)
if (!require(RColorBrewer)) install.packages("RColorBrewer")
Loading required package: RColorBrewer
library(RColorBrewer)

Un jeu de données interne à ggplot2

Nous utilisons un jeu de données interne à ggplot2 contenant le prix ainsi que d’autres attributs de 53 940 diamants.

Les commandes suivantes permettent de prendre connaissance du jeu de données.

## help(diamonds)
## View(diamonds)

head(diamonds)

str(diamonds)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   53940 obs. of  10 variables:
 $ carat  : num  0.23 0.21 0.23 0.29 0.31 0.24 0.24 0.26 0.22 0.23 ...
 $ cut    : Ord.factor w/ 5 levels "Fair"<"Good"<..: 5 4 2 4 2 3 3 3 1 3 ...
 $ color  : Ord.factor w/ 7 levels "D"<"E"<"F"<"G"<..: 2 2 2 6 7 7 6 5 2 5 ...
 $ clarity: Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 2 3 5 4 2 6 7 3 4 5 ...
 $ depth  : num  61.5 59.8 56.9 62.4 63.3 62.8 62.3 61.9 65.1 59.4 ...
 $ table  : num  55 61 65 58 58 57 57 55 61 61 ...
 $ price  : int  326 326 327 334 335 336 336 337 337 338 ...
 $ x      : num  3.95 3.89 4.05 4.2 4.34 3.94 3.95 4.07 3.87 4 ...
 $ y      : num  3.98 3.84 4.07 4.23 4.35 3.96 3.98 4.11 3.78 4.05 ...
 $ z      : num  2.43 2.31 2.31 2.63 2.75 2.48 2.47 2.53 2.49 2.39 ...

On ne va pas garder l’entier du tableau de données, mais seulement un échantillon, afin de ne pas surcharger ce notebook.

diam <- diamonds[sample(1:nrow(diamonds), 1000),]

Charger les données dans ggplot

Remarque : à partir d’ici, nous parlons principalement de ggplot sans le “2” car il va s’agir de la fonction “ggplot”.

Le “2” n’est utilisé que dans le nom du package.

ggplot1 existe.


On charge les données

Attention ! L’input doit toujours être un data frame !

g <- ggplot(diam)

g


Contrairement à la fonction plot, qui bricole un visuel lorsqu’on entre la commande plot(diamonds), avec ggplot il ne se passe rien.

Juste un grand carré gris.


Le résultat avec le package base.

## plot(diamonds)

La commande de base plot(diamonds) considère toutes les variables du data frame et les croise entre elles - numériques comme ordinales - pour un résultat illisible et inutile.

Qu’obtient-on avec la commande ggplot ?


str(g)
List of 9
 $ data       :Classes ‘tbl_df’, ‘tbl’ and 'data.frame':    1000 obs. of  10 variables:
  ..$ carat  : num [1:1000] 1.77 0.41 0.7 0.81 0.34 1 0.5 1.51 1.14 1 ...
  ..$ cut    : Ord.factor w/ 5 levels "Fair"<"Good"<..: 3 2 5 1 5 5 4 3 3 3 ...
  ..$ color  : Ord.factor w/ 7 levels "D"<"E"<"F"<"G"<..: 6 1 4 4 4 2 1 7 6 4 ...
  ..$ clarity: Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 4 3 2 6 4 3 2 6 3 8 ...
  ..$ depth  : num [1:1000] 60.2 63.8 61.2 64.8 62 62.3 58.5 60.6 63.5 58.3 ...
  ..$ table  : num [1:1000] 58 56 56 56.2 54 55 60 58 56 62 ...
  ..$ price  : int [1:1000] 13691 738 2270 3323 596 5396 1178 8789 4788 8002 ...
  ..$ x      : num [1:1000] 7.88 4.68 5.73 5.82 4.47 6.41 5.21 7.36 6.62 6.51 ...
  ..$ y      : num [1:1000] 7.83 4.72 5.78 5.98 4.5 6.34 5.19 7.4 6.58 6.57 ...
  ..$ z      : num [1:1000] 4.73 3 3.52 3.82 2.78 3.97 3.04 4.47 4.19 3.81 ...
 $ layers     : list()
 $ scales     :Classes 'ScalesList', 'ggproto' <ggproto object: Class ScalesList>
    add: function
    clone: function
    find: function
    get_scales: function
    has_scale: function
    input: function
    n: function
    non_position_scales: function
    scales: NULL
    super:  <ggproto object: Class ScalesList> 
 $ mapping    : list()
 $ theme      : list()
 $ coordinates:Classes 'CoordCartesian', 'Coord', 'ggproto' <ggproto object: Class CoordCartesian, Coord>
    aspect: function
    distance: function
    expand: TRUE
    is_linear: function
    labels: function
    limits: list
    range: function
    render_axis_h: function
    render_axis_v: function
    render_bg: function
    render_fg: function
    train: function
    transform: function
    super:  <ggproto object: Class CoordCartesian, Coord> 
 $ facet      :Classes 'FacetNull', 'Facet', 'ggproto' <ggproto object: Class FacetNull, Facet>
    compute_layout: function
    draw_back: function
    draw_front: function
    draw_labels: function
    draw_panels: function
    finish_data: function
    init_scales: function
    map: function
    map_data: function
    params: list
    render_back: function
    render_front: function
    render_panels: function
    setup_data: function
    setup_params: function
    shrink: TRUE
    train: function
    train_positions: function
    train_scales: function
    vars: function
    super:  <ggproto object: Class FacetNull, Facet> 
 $ plot_env   :<environment: R_GlobalEnv> 
 $ labels     : list()
 - attr(*, "class")= chr [1:2] "gg" "ggplot"

On retrouve le data frame contenant nos données, ainsi que les différents éléments propres à l’approche grammaticale des graphiques proposée par ggplot2, pour l’instant vides :

  • layers

  • scales

  • mapping

  • theme

  • coordinates

  • facet


À ce stade, le graphe est vide car nous n’avons défini ni mapping ni géométries (voir figure suivante).


g <- ggplot(diam)
g

Mapping

Avec un mapping mais sans géométrie

cf. figure suivante.

Pour rappel : ‘carat’ et ‘price’ sont des variables continues.


ggplot(diam, aes(x = carat, y = price)) 


On remarque que les échelles et les labels des axes sont déjà posés.

ggplot attend maintenant de savoir quoi dessiner.


Avec une géométrie mais sans mapping

Pas de figure…

ggplot(diam) + geom_point()
Error: geom_point requires the following missing aesthetics: x, y


Cette fois, ggplot n’a pas trouvé de mapping lui indiquant où poser son dessin, d’où l’erreur.


Avec un mapping et une géométrie

ggplot(diam, aes(x = carat, y = price)) + geom_point()


Pour comparaison, la commande la plus simple permettant d’obtenir (à peu près) le même résultat avec le package de base.

plot(diam$carat, diam$price)


Les échelles sont justes et les points sont bien situés, mais c’est tout et c’est moche.


C’est là le principe de ggplot : à partir de maintenant nous pouvons faire varier les éléments graphiques sans avoir à toucher aux données.

Par exemple, une interpolation.


ggplot(diam, aes(x = carat, y = price)) + geom_smooth()

Superposer des layers

Il suffit de les additionner pour les superposer.

Attention au ‘+’ à mettre à la fin de la ligne et pas au début.


ggplot(diam, aes(x = carat, y = price)) + 
  geom_point() + 
  geom_smooth()


Attention, l’ordre des géométries a une influence sur le graphique !


ggplot(diam, aes(x = carat, y = price)) + 
  geom_smooth() + 
  geom_point()


Dans le graphique précédent, la courbe d’interpolation a été dessinée avant les points.

Ainsi, elle apparaît dans la figure cachée sous ces derniers.

Différents mappings

Le mapping peut se faire à plusieurs endroits :


Tout dans ggplot

ggplot(diam, aes(x = carat, y = price)) + geom_point()


Dans les deux

ggplot(diam, aes(x = carat)) + geom_point(aes(y = price))


Tout dans la géométrie

ggplot(diam) + geom_point(aes(x = carat, y = price))


Bref, on peut mapper de nombreuses variables, en une seule fois, directement dans ggplot et elles seront reprises par les autres éléments.

Par exemple, l’attribut color de geom_point.

Remarque : la variable ‘clarity’ est ordinale.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point()


Grâce à ce seul mapping, ggplot attribue à chaque modalité une couleur, attribue aux points la couleur correspondante, et génère dans la foulée une légende (une des angoisses lorsqu’on travaille avec les commandes de base).

Pour l’anecdote, nous aurions obtenu le même résultat en faisant le mapping dans geom_point.


ggplot(diam, aes(x = carat, y = price)) + 
  geom_point(aes(color = clarity))


Attention à ne pas oublier de faire le mapping, c’est-à-dire d’utiliser la fonction aes(), sinon ça ne fonctionnera pas !

ggplot(diam, aes(x = carat, y = price)) + 
  geom_point(color = clarity)
Error in layer(data = data, mapping = mapping, stat = stat, geom = GeomPoint,  : 
  object 'clarity' not found

On trouve toutes les géométries disponibles ainsi que de nombreuses autres ressources indispensables dans l’indispensable cheat sheet de ggplot2 !


Variables et transformations, discrètes et continues

C’est hasardeux, mais on peut également appliquer une transformation continue (size) à une variable ordinale (cut).


ggplot(diam, aes(x = carat, y = price, color = clarity, size = cut)) + 
  geom_point()


Toutefois, il est recommandé d’appliquer une transformation continue (size) à une variable continue (par exemple depth) et une transformation discrète comme la couleur ou la forme (shape) à une variable discrète (par exemple cut).


ggplot(diam, aes(x = carat, y = price, color = clarity, shape = cut)) + 
  geom_point()


Détail qui a son importance dans l’exemple suivant : la fonction geom_smooth va hériter du mapping sur la couleur.

Note : “se = FALSE” empêche l’affichage de l’incertitude.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() + 
  geom_smooth(se = FALSE)


Pour le mapping sur la forme (au lieu de la couleur), on repassera.


ggplot(diam, aes(x = carat, y = price, shape = cut)) + 
  geom_point() + 
  geom_smooth(se = FALSE)


Et si l’on prend en considération les deux en même temps, ça peut mener à la catastrophe.


ggplot(diam, aes(x = carat, y = price, color = clarity, shape = cut)) + 
  geom_point() + 
  geom_smooth(se = FALSE)


Le même code que précédemment, avec cette fois le graphique à la place du message d’erreur.

ggplot(diam, aes(x = carat, y = price, color = clarity, shape = cut)) + 
  geom_point() + geom_smooth(se = FALSE)


Il est nécessaire de redistribuer le mapping plus subtilement.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point(aes(shape = cut)) + 
  geom_smooth(se = FALSE)


D’autres exemples concernant les attributs des éléments géométriques.


Transformation continue, variable continue

ggplot(diam, aes(x = carat, y = price, color = clarity, size = depth)) + 
  geom_point()


Il est possible évidemment de modifier plus subtilement la taille des sommets lorsqu’on fait un mapping dessus, si les valeurs par défaut ne nous plaisent pas.

En général, cela passe par les fonctions scale.

Elles commencent par scale_ (voir la cheat sheet).

Nous reviendrons plus en détail là-dessus.

Dans la figure suivante, nous donnons des valeurs minimales et maximales pour la taille des sommets.


ggplot(diam, aes(x = carat, y = price, color = clarity, size = depth)) + 
  geom_point() + 
  scale_size(range = c(1,3))


Remarque : l’effet est difficile à observer car la variance est très petite.

summary(diam$depth)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  55.90   61.10   61.90   61.79   62.50   68.40 
sd(diam$depth)
[1] 1.410995

Au passage, remarquons que l’on peut aussi utiliser l’attribut size de geom_point sans mapping.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point(size = 1)


Dans ce cas, la taille des points est considérée hors mapping et donc indépendamment d’une quelconque variable.


Remarquons également l’organisation hiérarchique des mappings et des transformations : dans l’exemple qui suit, la variable depth est tout d’abord mappée sur la taille des sommets.

Puis, dans geom_point, on lui attribue une valeur fixe.

Arrivée ensuite, c’est cette dernière qui l’emporte sur le mapping initial.


ggplot(diam, aes(x = carat, y = price, color = clarity, size = depth)) + 
  geom_point(size = 1)


Au passage, la modification du titre en légende dépend de la fonction scale correspondante (ici scale_size).

C’est logique, mais contre-intuitif pour qui aura passé beaucoup (trop ?) de temps avec les commandes graphiques de base dans R.


ggplot(diam, aes(x = carat, y = price, color = clarity, size = depth)) + 
  geom_point() + 
  scale_size("DEPTH", range = c(1,3))

Bilan intermédiaire

Nous avons vu comment :


Il nous reste à découvrir :

Le facettage

Attention ! Ceci s’applique à des variables discrètes.

Le facettage divise le jeu de données en fonction des catégories d’une variable.

Dans un sens…


ggplot(diam, aes(x = carat, y = price)) + 
  geom_point() +
  facet_grid(. ~ cut) 


… et dans l’autre.

(Remarquez la position inversée de la variable cut dans facet_grid().)


ggplot(diam, aes(x = carat, y = price)) + 
  geom_point() +
  facet_grid(cut ~ .) 


En croisant deux variables discrètes.


ggplot(diam, aes(x = carat, y = price)) + 
  geom_point() +
  facet_grid(color ~ clarity) 


Finalement, en croisant deux variables discrètes, avec un mapping sur la couleur.


ggplot(diam, aes(x = carat, y = price, color = cut)) + 
  geom_point() +
  facet_grid(color ~ clarity) 


Il y a une autre option de facettage lorsqu’on n’utilise qu’une seule variable discrète : facet_wrap.

Dans ce cas, remarquez que nous n’utilisons plus le point (.) avant le tilde (~).


ggplot(diam, aes(x = carat, y = price)) + 
  geom_point() +
  facet_wrap(~ clarity) 

Les échelles

Elles commencent toutes par scale_

Ensuite, on complète avec le nom de la variable concernée.


scale_alpha scale_alpha_continuous scale_alpha_discrete scale_alpha_identity scale_alpha_manual scale_color_brewer scale_color_continuous scale_color_discrete scale_color_distiller scale_color_gradient scale_color_gradient2 scale_color_gradientn scale_color_grey scale_color_hue scale_color_identity scale_color_manual scale_colour_brewer scale_colour_continuous scale_colour_date scale_colour_datetime scale_colour_discrete scale_colour_distiller scale_colour_gradient scale_colour_gradient2 scale_colour_gradientn scale_colour_grey scale_colour_hue scale_colour_identity scale_colour_manual scale_continuous scale_date scale_fill_brewer scale_fill_continuous scale_fill_date scale_fill_datetime scale_fill_discrete scale_fill_distiller scale_fill_gradient scale_fill_gradient2 scale_fill_gradientn scale_fill_grey scale_fill_hue scale_fill_identity scale_fill_manual scale_identity scale_linetype scale_linetype_continuous scale_linetype_discrete scale_linetype_identity scale_linetype_manual scale_manual scale_radius scale_shape scale_shape_continuous scale_shape_discrete scale_shape_identity scale_shape_manual scale_size scale_size_area scale_size_continuous scale_size_date scale_size_datetime scale_size_discrete scale_size_identity scale_size_manual scale_x_continuous scale_x_date scale_x_datetime scale_x_discrete scale_x_log10 scale_x_reverse scale_x_sqrt scale_y_continuous scale_y_date scale_y_datetime scale_y_discrete scale_y_log10 scale_y_reverse scale_y_sqrt


Par exemple, si l’on travaille sur la couleur, on pourra faire varier la palette des couleurs en modifiant le nom de l’échelle. Faites le test en écrivant scale_color_ dans la console puis en pressant sur la touche tab pour voir les suggestions…


En gris

ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() +
  scale_color_grey()


La version par défaut.

ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() +
  scale_color_discrete()


ColorBrewer, nommée d’après une de ses auteurs, Cnythia Brewer, est une librairie de couleurs précalculées qui s’accordent bien.

On peut les utiliser ici avec la fonction scale_color_brewer.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() +
  scale_color_brewer()


En changeant de palette.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() +
  scale_color_brewer(palette = 2)


Attention si vous utilisez la mauvaise échelle : soit il ne se passera rien (comme dans la figure suivante), soit il y aura un message d’erreur.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() +
  scale_fill_brewer(palette = 2)


Dans la figure suivante, nous changeons la forme utilisée pour dessiner les points afin qu’il y ait un pourtour (color) et un contenu (fill).

Cette transformation a été effectuée en donnant comme instruction que les points doivent changer de forme (indépendamment de toute variable).

Nous en profitons pour dessiner l’intérieur. Quelle fonction faut-il utiliser ?


ggplot(diam, aes(x = carat, y = price, fill = clarity)) + 
  geom_point(shape = 21) +
  scale_fill_brewer(palette = 2)


Mais les échelles, ça ne concerne pas seulement l’intérieur du graphique.

Nous utilisons également des échelles sur les axes.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() + 
  scale_x_continuous(breaks = c(1,3), minor_breaks = c(sqrt(2), pi)) +
  scale_y_continuous(breaks = sample(20000, 10))


Échelle logarithmique FTW.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() + 
  scale_y_log10()

Les annotations

Via un mapping, par exemple pour un MDS.


ggplot(diam, aes(x = carat, y = price, label = clarity)) + 
  geom_text()


L’annotation manuelle est possible, à l’ancienne, mais pas forcément recommandée.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() +
  annotate("text", x = 3.5, y = 10000, label = "HELLO")

D’autres types de graphiques

C’est la seule fois que nous voyons une transformation statistique dans cette présentation (malheureusement).

Ce sont les fonctions commençant par stat_.

Pour plus d’infos, voir la cheat sheet.


ggplot(diam, aes(price)) +
  geom_area(stat = "bin")


ggplot(diam, aes(price)) +
  geom_density(kernel = "gaussian")


ggplot(diam, aes(price)) +
  geom_histogram(binwidth = 30)


Avec une variable discrète, cette fois.

ggplot(diam, aes(color)) +
  geom_bar()


Une variable discrète et une variable continue.

ggplot(diam, aes(x = color, y = price)) +
  geom_boxplot()


Deux variables discrètes.

ggplot(diam, aes(x = cut, y = color)) +
  geom_count()


ggplot(diamonds, aes(x=price, fill=cut)) + 
  geom_histogram()


Distributions bi-variées.

ggplot(diamonds, aes(carat, price)) +
  geom_bin2d(binwidth = c(0.25, 500))


ggplot(diamonds, aes(carat, price)) +
  geom_hex()

Les thèmes

La fonction ggtitle est utilisée pour choisir un titre.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() + 
  ggtitle("Mon joli graphique")


Et, classique pour une fois, xlab et ylab pour changer les noms des axes.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() + 
  xlab("Ma jolie abscisse") + 
  ylab("Ma jolie ordonnée")


Pour varier les thèmes : theme_.


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() +
  theme_bw()


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() +
  theme_dark()


ggplot(diam, aes(x = carat, y = price, color = clarity)) + 
  geom_point() +
  theme_minimal()

Sauver un graphique

ggsave
function (filename, plot = last_plot(), device = NULL, path = NULL, 
    scale = 1, width = NA, height = NA, units = c("in", "cm", 
        "mm"), dpi = 300, limitsize = TRUE, ...) 
{
    dev <- plot_dev(device, filename, dpi = dpi)
    dim <- plot_dim(c(width, height), scale = scale, units = units, 
        limitsize = limitsize)
    if (!is.null(path)) {
        filename <- file.path(path, filename)
    }
    dev(file = filename, width = dim[1], height = dim[2], ...)
    on.exit(utils::capture.output(grDevices::dev.off()))
    grid.draw(plot)
    invisible()
}
<environment: namespace:ggplot2>

Par exemple

ggsave("plot.pdf", width = 7, height = 7)

Bien préparer ses données

«Tidy data is a standard way of mapping the meaning of a dataset to its structure.»

«A dataset is messy or tidy depending on how rows, columns and tables are matched up with observations, variables and types.»


«In tidy data:

  1. Each variable forms a column.
  2. Each observation forms a row.
  3. Each type of observational unit forms a table.»

Source :

# library(tidyr)
# vignette("tidy-data")

Marie-Louise Timcke a proposé sur journocode.com une très bonne ressource à ce sujet.

En particulier, elle présente l’exemple suivant, extrait d’explications d’Hadley Wickham.


Le format tidy, ou long form, est fortement recommandé dans ggplot2.

LS0tCnRpdGxlOiAnUGFyY291cnMgaHVtYW5pdMOpcyBudW3DqXJpcXVlcycKc3VidGl0bGUgOiAnVmlzdWFsaXNhdGlvbiBkZSBkb25uw6llcyBhdmVjIFInCmF1dGhvcjogIllhbm5pY2sgUm9jaGF0IgpkYXRlOiAiMTIgZsOpdnJpZXIgMjAxOCIKb3V0cHV0OgogIGlvc2xpZGVzX3ByZXNlbnRhdGlvbjogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgotLS0KCiMjIyBPYmplY3RpZnMgZHUgY291cnMKCiogUXUnZXN0LWNlIHF1ZSBSIGV0IHF1ZWxsZXMgcG9zc2liaWxpdMOpcyBzb250IG9mZmVydGVzIHBhciBjZSBsYW5nYWdlID8KKiBRdWFuZCB5IGF2b2lyIHJlY291cnMgZXN0LWlsIHBlcnRpbmVudCBkYW5zIGxlIGNhZHJlIGRlIG1hIHJlY2hlcmNoZSA/CiogT8O5IHNlIHRyb3V2ZW50IGxlcyByZXNzb3VyY2VzIHBvdXIgbCd1dGlsaXNlciA/CgotLS0KCkxlIGRvY3VtZW50IHBvc3PDqWRhbnQgbCdleHRlbnNpb24gIi5SbWQiIGVzdCBhdSBmb3JtYXQgbWFya2Rvd24gKGNvbW1lIHdpa2lwZWRpYSkgZXQgcGV1dCDDqnRyZSBhbm5vdMOpIGVuIHRvdXQgdGVtcHMuCgpOJ2jDqXNpdGV6IHBhcyDDoCBwcmVuZHJlIHZvcyBub3RlcyBkaXJlY3RlbWVudCBkYW5zIGNlIGRvY3VtZW50LgoKLS0tCgojIyMgUXVlbHF1ZXMgcmVzc291cmNlcyB1dGlsZXMKCi0tLQoKKipDb21wdXRhdGlvbmFsIEhpc3RvcmljYWwgVGhpbmtpbmcgV2l0aCBBcHBsaWNhdGlvbnMgaW4gUioqIGRlIExpbmNvbG4gQS4gTXVsbGVuIChpbmNvbXBsZXQpCgpodHRwOi8vZGgtci5saW5jb2xubXVsbGVuLmNvbS8KCkxlIHN5bGxhYnVzIGRlIHNvbiBjb3Vycy4KCmh0dHA6Ly9saW5jb2xubXVsbGVuLmNvbS9jb3Vyc2VzL2NsaW8yLjIwMTgvCgoqKlRoZSBIaXN0b3JpYW4ncyBNYWNyb3Njb3BlOiBCaWcgRGlnaXRhbCBIaXN0b3J5KiogZGUgU2hhd24gR3JhaGFtLCBJYW4gTWlsbGlnYW4gZXQgU2NvdHQgV2VpbmdhcnQKCmh0dHA6Ly93d3cudGhlbWFjcm9zY29wZS5vcmcvP3BhZ2VfaWQ9NzA3CgpMZSBsaXZyZSBhIMOpdMOpIHB1Ymxpw6kgZGVwdWlzLCB2b2lyIGh0dHA6Ly93d3cudGhlbWFjcm9zY29wZS5vcmcvMi4wLwoKLS0tCgpVbmUgcmVzc291cmNlIGVuIGZyYW7Dp2Fpcy4KCioqSW50cm9kdWN0aW9uIMOgIFIgZXQgYXUgdGlkeXZlcnNlKiogZGUgSnVsaWVuIEJhcm5pZXIKCmh0dHBzOi8vanViYS5naXRodWIuaW8vdGlkeXZlcnNlCgotLS0tCgoqKlIgZm9yIERhdGEgU2NpZW5jZSoqIGRlIEdhcnJldHQgR3JvbGVtdW5kICYgSGFkbGV5IFdpY2toYW0KCmh0dHA6Ly9yNGRzLmhhZC5jby5uei8KCioqQW4gSW50cm9kdWN0aW9uIHRvIFN0YXRpc3RpY2FsIGFuZCBEYXRhIFNjaWVuY2VzIHZpYSBSKiogZGUgQ2hlc3RlciBJc21heSAmIEFsYmVydCBZLiBLaW0KCmh0dHA6Ly9tb2Rlcm5kaXZlLmNvbS8gCgoqKkV4Y2VsIHZzIFI6IEEgQnJpZWYgSW50cm9kdWN0aW9uIHRvIFIqKgoKaHR0cHM6Ly93d3cuamVzc2VzYWRsZXIuY29tL3Bvc3QvZXhjZWwtdnMtci8KCioqVGhlIFByb2dyYW1taW5nIEhpc3RvcmlhbioqCgpodHRwczovL3Byb2dyYW1taW5naGlzdG9yaWFuLm9yZy8gCgotLS0tCgoqKkxlcyBjaGVhdHNoZWV0cyBkZSBSU3R1ZGlvKioKCmh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Jlc291cmNlcy9jaGVhdHNoZWV0cy8KCi0tLS0KCiMjIyBRdSdlc3QtY2UgcXVlIFIgPyAKClIgZXN0IHVuIGxhbmdhZ2UgZGUgcHJvZ3JhbW1hdGlvbiDDoCBsJ29yaWdpbmUgcHLDqXZ1IHBvdXIgZmFpcmUgZGUgbCdhbmFseXNlIHN0YXRpc3RpcXVlLgoKUiBlc3QgZGlzcG9uaWJsZSBzb3VzIGxpY2VuY2UgbGlicmUuIElsIGVzdCBiYXPDqSBzdXIgbGUgbGFuZ2FnZSBwcm9wcmnDqXRhaXJlIFMuCgpTb24gdXNhZ2Ugcydlc3QgcsOpcGFuZHUgYmllbiBhdS1kZWzDoCBkZXMgc3RhdGlzdGlxdWVzLgoKLS0tCgpNYWlzIFIgZMOpc2lnbmUgw6lnYWxlbWVudCB1bmUgaW50ZXJmYWNlIGRlIHByb2dyYW1tYXRpb24gcXVlIGwnb24gbm9tbWUgUiBHVUksICJHVUkiIHNpZ25pZmlhbnQgIkdyYXBoaWNhbCBVc2VyIEludGVyZmFjZSIuCgpSIEdVSSBlc3QgdW4gbG9naWNpZWwgbGlicmUuCgotLS0KCiMjIyBRdSdlc3QtY2UgcXVlIFJTdHVkaW8gPwoKUlN0dWRpbyBlc3QgdW4gZW52aXJvbm5lbWVudCBwZXJtZXR0YW50IGRlIHByb2dyYW1tZXIgZW4gUiwgYydlc3Qtw6AtZGlyZSB1biBwZXUgcGx1cyBxdSd1bmUgc2ltcGxlIGludGVyZmFjZSBncmFwaGlxdWUuCgotLS0KCkVuIHBsdXMgZCdhZmZpY2hlciB1bmUgY29uc29sZSAobMOgIG/DuSBsZSBjb2RlIGVzdCBleMOpY3V0w6ksIGfDqW7DqXJhbGVtZW50IGRhbnMgbGEgZmVuw6p0cmUgZW4gYmFzIMOgIGdhdWNoZSkgZXQgdW5lIGZlbsOqdHJlIGRlIHNjcmlwdCAobMOgIG/DuSBsJ29uIHLDqWRpZ2UgbGUgY29kZSBxdWUgbCdvbiB2ZXV0IHLDqXV0aWxpc2VyLCBnw6luw6lyYWxlbWVudCBlbiBoYXV0IMOgIGdhdWNoZSksIFJTdHVkaW8gcGVybWV0IHBhciBleGVtcGxlIGRlIGNvbnNlcnZlciBiaWVuIGVuIHZ1ZSBsYSBsaXN0ZSBkZXMgdmFyaWFibGVzIGVucmVnaXN0csOpZXMgZW4gbcOpbW9pcmUgKGVuIGhhdXQgw6AgZHJvaXRlKSwgb3UgZCdhY2PDqWRlciBhaXPDqW1lbnQgw6AgZGVzIGZpY2hpZXJzIGQnYWlkZSAoZW4gYmFzIMOgIGRyb2l0ZSkuCgpSU3R1ZGlvIHBlcm1ldCDDqWdhbGVtZW50IGQndXRpbGlzZXIgdW4gbm90ZWJvb2sgY29tbWUgY2VsdWktY2kuCgotLS0KCiMjIyBDb21tZW50IGZvbmN0aW9ubmVudCBsZXMgbm90ZWJvb2tzID8KCkNlIGRvY3VtZW50IHF1ZSB2b3VzIGF2ZXogb3V2ZXJ0IGRhbnMgUlN0dWRpbyBvdSBkYW5zIHVuIG5hdmlnYXRldXIgd2ViIGVzdCB1biBub3RlYm9vay4KCi0tLQoKQydlc3QgdW4gZm9ybWF0IGRlIGRvY3VtZW50IHBlcm1ldHRhbnQgZGUgcHLDqXNlbnRlciBkdSB0ZXh0ZSwgZHUgY29kZSwgZXQgbGVzIHLDqXN1bHRhdHMgZGUgbCdleMOpY3V0aW9uIGR1IGNvZGUuCgotLS0KCkRhbnMgbGEgcmVjaGVyY2hlIHNjaWVudGlmaXF1ZSwgY2UgZm9ybWF0IHBlcm1ldCBkZSByw6lwbGlxdWVyIHVuZSBleHDDqXJpZW5jZS4KCkxhIGTDqW1hcmNoZSBkZSBwYXJ0YWdlciB1biBub3RlYm9vayBlbiBtw6ptZSB0ZW1wcyBxdSd1biBhcnRpY2xlIHNjaWVudGlmaXF1ZSBkZXZyYWl0IMOgIGwnYXZlbmlyIGRldmVuaXIgdW4gc3RhbmRhcmQuCgotLS0KCkxlIHN5c3TDqG1lIGRlIG5vdGVib29rcyBsZSBwbHVzIHLDqXBhbmR1IGVzdCBKdXB5dGVyLCBxdWkgZm9uY3Rpb25uZSBhdmVjIGxlcyBsYW5nYWdlcyBkZSBwcm9ncmFtbWF0aW9uIEp1bGlhLCBQeXRob24gZXQgUiwgbWFpcyBwZXJtZXQgYXVzc2kgZGUgZ8OpcmVyIGQnYXV0cmVzIGxhbmdhZ2VzIGRlIHByb2dyYW1tYXRpb24uCgotLS0KCkljaSwgbm91cyB1dGlsaXNvbnMgZGVzIG5vdGVib29rcyBhdSBmb3JtYXQgcHLDqXZ1IHBhciBSU3R1ZGlvLiAKClVuIG5vdGVib29rIHBldXQgc2UgZMOpY2xpbmVyIGVuIHBsdXNpZXVycyBmb3JtYXRzLgoKKiBMZSBmaWNoaWVyIGF1IGZvcm1hdCBgLlJtZGAgcydvdXZyZSBkYW5zIFJTdHVkaW8uIAoqIExlIGZpY2hpZXIgYXUgZm9ybWF0IGAubmIuaHRtbGAgcydvdXZyZSBkYW5zIHVuIG5hdmlnYXRldXIuCiogTGUgZmljaGllciBhdSBmb3JtYXQgYC5odG1sYCBjb250aWVudCBsZXMgc2xpZGVzIChkYW5zIGxlIGNhcyBwcsOpc2VudCkuCgotLS0KCkxlIGNvZGUgYXBwYXJhw650IHN5c3TDqW1hdGlxdWVtZW50IGRhbnMgdW5lIGNhc2Ugc3VyIGZvbmQgZ3Jpcy4KCkxlIHLDqXN1bHRhdCBkdSBjb2RlIGFwcGFyYcOudCBlbnN1aXRlIGRhbnMgdW5lIGNhc2Ugc3VyIGZvbmQgYmxhbmMgb3UgZ3JpcyBzZWxvbiBsZXMgcsOpZ2xhZ2VzLgoKYGBge3J9CmZvciAoaSBpbiAxOjMpIHByaW50KExFVFRFUlNbaV0pCmBgYAoKCi0tLQoKRW4gb3V2cmFudCBsZSBub3RlYm9vayBkYW5zIFJTdHVkaW8sIHZvdXMgYXVyZXogbGEgcG9zc2liaWxpdMOpIGRlIG1vZGlmaWVyIGxlIGNvZGUgZXQgZGUgbGUgcsOpZXjDqWN1dGVyLgoKLS0tCgpQYXIgZXhlbXBsZSwgZGFucyBsZSBjb2RlIGNpLWRlc3NvdXMsIG5vdXMgc2F1dm9ucyBsYSB2YWxldXIgIjIiIGRhbnMgbGEgdmFyaWFibGUgImEiIGV0IGxhIHZhbGV1ciAiZSIgZGFucyBsYSB2YXJpYWJsZSAiYiIuIFB1aXMgbm91cyDDqWxldm9ucyBhIMOgIGxhIHB1aXNzYW5jZSBiLCBjJ2VzdC3DoC1kaXJlIDIgw6AgbGEgcHVpc3NhbmNlIDMuIAoKTGUgcsOpc3VsdGF0IHMnYWZmaWNoZSBlbi1kZXNzb3VzLgoKYGBge3J9CmEgPC0gMgpiIDwtIDMKYV5iCmBgYAoKLS0tCgojIyMgRXhlcmNpY2UgMS4xCgoqIFJlcHJlbmV6IGwnZXhlbXBsZSBjb250ZW5hbnQgZHUgY29kZSBjaS1kZXNzdXMuCiogTW9kaWZpZXogbGVzIHZhbGV1cnMgZGUgYSBldCBkZSBiIChwYXIgZXhlbXBsZSBhdmVjIGEgPSAxMCBldCBiID0gLTEpLgoqIEFjdHVhbGlzZXogbGUgY2FsY3VsLgoKLS0tCgojIyMgT2J0ZW5pciBkZSBsJ2FpZGXigKYKCuKApiBxdWFuZCBvbiBjb25uYcOudCBsZSBub20gZGUgbGEgZm9uY3Rpb24uCgpgYGB7ciBldmFsID0gRkFMU0V9CmhlbHAoZXhwKSAgICAgICAgICAgICAgICAgICAgICMjIExhIHBhZ2UgZCdhaWRlIGQndW5lIHZhcmlhYmxlCj9leHAgICAgICAgICAgICAgICAgICAgICAgICAgICMjIFVuZSBhdXRyZSBzeW50YXhlIHBvdXIgbGUgbcOqbWUgcsOpc3VsdGF0CmBgYAoKTGEgdG91Y2hlIGRlIHRhYnVsYXRpb24gZXN0IHV0aWxlIGRhbnMgbGUgY2FzIG/DuSBsZXMgcHJlbWnDqHJlcyBsZXR0cmVzIGQndW5lIGZvbmN0aW9uIHNvbnQgY29ubnVlcy4KCi0tLQoK4oCmIHF1YW5kIG9uIG5lIGNvbm5hw650IHBhcyBsZSBub20gZGUgbGEgZm9uY3Rpb24gbWFpcyBzZXVsZW1lbnQgY2VsdWkgZGUgbGEgbcOpdGhvZGUuCgpgYGB7ciBldmFsID0gRkFMU0V9CmhlbHAuc2VhcmNoKCJsaW5lYXIgbW9kZWwiKSAgICMjIENoZXJjaGVyIGRhbnMgbGVzIHBhZ2VzIGQnYWlkZQo/PyJsaW5lYXIgbW9kZWwiICAgICAgICAgICAgICAjIyBVbmUgYXV0cmUgc3ludGF4ZSBwb3VyIGxlIG3Dqm1lIHLDqXN1bHRhdApgYGAKCgotLS0KCkRhbnMgY2Ugbm90ZWJvb2sgY29tbWUgZGFucyBsZXMgc2NyaXB0cyBxdWUgdm91cyB0cm91dmVyZXogZW4gbGlnbmUsIGxlcyBmb25jdGlvbnMgcGV1dmVudCBzZW1ibGVyIHNvcnRpciBkJ3VuIGNoYXBlYXUgY29tbWUgc2kgZGUgcmllbiBuJ8OpdGFpdC4KCk5lIHBhcyBzZSBmYWlyZSBkJ2lsbHVzaW9uczogCgoqIElsIGEgZCdhYm9yZCBmYWxsdSBkw6ljb3V2cmlyIGxldXIgZXhpc3RlbmNlLgoqIFB1aXMgY29tcHJlbmRyZSBsZXVyIGZvbmN0aW9ubmVtZW50LgoqIChMZSBwbHVzIHNvdXZlbnQpIGVuIGZhaXNhbnQgZGVzIGVycmV1cnMgYXUgcGFzc2FnZS4KCi0tLQoKQydlc3QgcG91ciBjZXR0ZSByYWlzb24gcXUnaWwgbmUgZmF1dCAqKmphbWFpcyoqIGjDqXNpdGVyIMOgIGZhaXJlIHVuZSByZWNoZXJjaGUgCgoqIGRhbnMgbGVzIHBhZ2VzIGQnYWlkZSBkZSBSCiAgKyBhdmVjIGA/YCBsb3JzcXUnb24gY29ubmHDrnQgbGEgZm9uY3Rpb24uCiAgKyBhdmVjIGA/P2AgbG9yc3F1J29uIG5lIGxhIGNvbm5hw650IHBhcy4KKiBlbiBsaWduZSAKICArIHN1ciBzdGFja292ZXJmbG93CiAgKyBzdXIgdW4gbW90ZXVyIGRlIHJlY2hlcmNoZQogIAotLS0KCiMjIyBBdXRyZXMgcmVzc291cmNlcyBlbiBsaWduZSAodmVpbGxlKQoKKiBTdXIgVHdpdHRlciwgbGUgaGFzaHRhZyAjUnN0YXRzCiogU3VyIFItYmxvZ2dlciwgdW4gYWdyw6lnYXRldXIgZGUgYmxvZ3MgZG9udCBsZSB0aMOobWUgZXN0IGxlIGxhbmdhZ2UgUiBodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS8KKiBTdXIgbGVzIG1haWxpbmcgbGlzdHMgaHR0cHM6Ly93d3cuci1wcm9qZWN0Lm9yZy9tYWlsLmh0bWwgCgotLS0KCiMjIyBFeGVyY2ljZSAxLjIKCiogQ2FsY3VsZXogbCdleHBvbmVudGllbGxlIGRlIGBiYCAoZW4gYW5nbGFpcyA6ICJleHBvbmVudGlhbCIpLgoqIENhbGN1bGV6IGxhIHJhY2luZSBjYXJyw6llIGRlIGBhYCAoZW4gYW5nbGFpcyA6ICJzcXVhcmUgcm9vdCIpLgoKLS0tCgpMYSBjaGVhdHNoZWV0IHBvdXIgbCdpbXBvcnRhdGlvbiBldCBsJ2V4cG9ydGF0aW9uIGRlIGRvbm7DqWVzLgoKaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8vY2hlYXRzaGVldHMvcmF3L21hc3Rlci9kYXRhLWltcG9ydC5wZGYKCi0tLS0KCiMjIyBMZXMgZm9uY3Rpb25zCgpVbmUgZm9uY3Rpb24gcHLDqWTDqWZpbmllLgoKYGBge3J9CnkgPSBzcXJ0KDQpICAgICMjIEZvbmN0aW9uIHJhY2luZSBjYXJyw6llCnkKYGBgCgotLS0tCgpUb3V0ZXMgbGVzIGZvbmN0aW9ucyBkdSBwYWNrYWdlIGBiYXNlYC4KCmBgYHtyIGV2YWwgPSBGQUxTRX0KaGVscChwYWNrYWdlPSJiYXNlIikKYGBgCgoKLS0tLQoKVW5lIGZvbmN0aW9uIGNvbnN0cnVpdGUgcG91ciBsJ29jY2FzaW9uLgoKYGBge3J9CmYgPC0gZnVuY3Rpb24oeCkgezIqeH0KZigxNykKZihwaSkKYGBgCgotLS0tCgojIyMgTGVzIGNsYXNzZXMKCiogImNoYXJhY3RlciIKKiAiY29tcGxleCIKKiAiZG91YmxlIgoqICJleHByZXNzaW9uIgoqICJpbnRlZ2VyIgoqICJsaXN0IgoqICJsb2dpY2FsIgoqICJudW1lcmljIgoqICJzaW5nbGUiCiogInJhdyIKKiAidmVjdG9yIgoqICJmdW5jdGlvbiIKKiDigKYKCi0tLS0KCkNvbm5hw650cmUgbGEgY2xhc3NlIGQndW4gb2JqZXQuCgpgYGB7cn0KeiA8LSAiaGVsbG8iCmNsYXNzKHopICAgICAgICAgICAgICAgICAgICAgIyMgTGUgdHlwZSBkJ3VuZSB2YXJpYWJsZQpgYGAKCi0tLS0KCkxhIGZvbmN0aW9uIGBzdHJgIHBlcm1ldCBkJ29idGVuaXIgZGVzIGluZm9ybWF0aW9ucyBwbHVzIGNvbXBsw6h0ZXMuCgpgYGB7cn0Kc3RyKHopCmBgYAoKLS0tLQoKYGBge3J9CnZlYyA8LSBjKDMsNSwzLDcpCnN0cih2ZWMpCmBgYAoKLS0tLQoKYGBge3J9CmYgPC0gZnVuY3Rpb24oeCkgezIqeH0Kc3RyKGYpCmBgYAoKLS0tLQoKYGBge3J9CmEgPC0gZigxNykKc3RyKGEpCmBgYAoKLS0tLQoKIyMjIENoYcOubmVzIGRlIGNhcmFjdMOocmVzCgpMZXMgY2hhw65uZXMgZGUgY2FyYWN0w6hyZXMgc29udCBkZXMgYXNzZW1ibGFnZXMgZGUgc3ltYm9sZXMuCgpgYGB7cn0KYiA8LSAidW5pbCIKYgpgYGAKCi0tLS0KCklsIGV4aXN0ZSBkZSBub21icmV1c2VzIGZvbmN0aW9ucyBwb3VyIGxlcyBtYW5pcHVsZXIuIAoKUGFyIGV4ZW1wbGUgcG91ciBjb25uYcOudHJlIGxldXIgbG9uZ3VldXIuCgpgYGB7cn0KbmNoYXIoYikKYGBgCgotLS0tCgpQb3VyIG1hbmlwdWxlciBkdSB0ZXh0ZSBkZSBtYW5pw6hyZSBhdmFuY8OpZSBkYW5zIFIgKG91IGRhbnMgbidpbXBvcnRlIHF1ZWwgYXV0cmUgbGFuZ2FnZSBkZSBwcm9ncmFtbWF0aW9uKSwgaWwgcGV1dCDDqnRyZSBuw6ljZXNzYWlyZSBkJ2FwcHJlbmRyZSDDoCBtYW5pcHVsZXIgZGVzIGV4cHJlc3Npb25zIHLDqWd1bGnDqHJlcy4KCi0tLS0KCkRldXggdHV0b3JpYXV4IHBvdXIgUi4KCiogaHR0cDovL3N0cmluZ3IudGlkeXZlcnNlLm9yZy9hcnRpY2xlcy9yZWd1bGFyLWV4cHJlc3Npb25zLmh0bWwKKiBodHRwOi8vc3RhdDU0NS5jb20vYmxvY2swMjJfcmVndWxhci1leHByZXNzaW9uLmh0bWwKCi0tLS0KCkxhIGNoZWF0c2hlZXQsIGluZGlzcGVuc2FibGUuCgpodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9yYXcvbWFzdGVyL3N0cmluZ3MucGRmCgotLS0tCgpQb3VyIHRlc3RlciBkZXMgZXhwcmVzc2lvbnMgcsOpZ3VsacOocmVzIDoKCmh0dHBzOi8vcmVnZXgxMDEuY29tLwoKLS0tLQoKUXVlbHF1ZXMgcmVzc291cmNlcyBsdWRpcXVlcyBwb3VyIHMnZW50cmHDrm5lcuKApgoKKiBodHRwczovL2FsZi5udS9SZWdleEdvbGYgCiogaHR0cHM6Ly9yZWdleGNyb3Nzd29yZC5jb20vCiogaHR0cHM6Ly9yZWdleG9uZS5jb20vCgotLS0tCgojIyMgTGVzIMOpbMOpbWVudHMgbG9naXF1ZXMKCiogVFJVRSBldCBGQUxTRQoKTWFpcyBhdXNzaeKApgoKKiBOQSwgTmFOCiogSW5mCgoiTkEiIHNpZ25pZmllICJOb3QgYXZhaWxhYmxlIi4KCiJOYU4iIHNpZ25pZmllICJOb3QgYSBOdW1iZXIiLgoKLS0tLQoKCiMjIyBMZXMgb3DDqXJhdGV1cnMgZGUgY29tcGFyYWlzb24KCgpgYGB7ciBlcnJvciA9IFRSVUV9CjEgPT0gMQoxID09IDIKYGBgCgotLS0KCmBgYHtyfQoxICE9IDEgCjEgIT0gMgpgYGAKCi0tLS0KCmBgYHtyfQoxIDwgMQoxIDw9IDEKYGBgCgotLS0tCgoKIyMgTGVzIHZlY3RldXJzCgoKYGBge3J9CnggPC0gYyg1LDQsNSw2LDcsOCkgCngKeF9jaGFyID0gYygiYSIsImIiLCJjIikgCnhfY2hhcgpgYGAKCi0tLS0KCkFjY8OpZGVyIGRpcmVjdGVtZW50IMOgIHVuIMOpbMOpbWVudCBkJ3VuIHZlY3RldXIKCmBgYHtyfQp4CnhbMl0KeFtjKDIsNCldCmBgYAoKLS0tCmBgYHtyfQp4CnhbYygtMiwtNCldCmBgYAoKCi0tLS0KCk1hbmlwdWxlciBkZXMgdmVjdGV1cnMKCmBgYHtyfQp4WzZdID0gMTAKeApgYGAKCi0tLQoKYGBge3J9CmEgPSBjKDMsNCw1LDYpCmFbYygyLDMpXSA9IDAKYQpgYGAKCi0tLQoKYGBge3J9CmFbYygyLDMpXSA9IGMoOCw3KQphCmBgYAoKLS0tLQoKYGBge3J9CmMoMSwyLDMpICsgYygzLDQsNSkKYGBgCgotLS0tCgpgYGB7cn0KNCAqIGMoMSwyLDMpCmMoMiw1KSA8IDQKYGBgCgotLS0tCgpBdHRlbnRpb24gbG9yc3F1ZSBkZXV4IMOpbMOpbWVudHMgbmUgc29udCBwYXMgZGUgbcOqbWUgdGFpbGxlICEKCmBgYHtyfQpjKDEsMikgKyBjKDMsNCw1LDYpCmBgYAoKLS0tLQoKQXR0ZW50aW9uIGxvcnNxdWUgbCd1biBuJ2VzdCBwYXMgbXVsdGlwbGUgZGUgbCdhdXRyZSAhCgpgYGB7cn0KYygxLDIpICsgYygyLDMsNCkKYGBgCgotLS0tCgojIyBDb21tZW50IGNvbm5hw650cmUgbGUgdHlwZSBkJ3VuZSB2YXJpYWJsZSA/CgpgYGB7cn0KYSA8LSAxCmIgPC0gMTo1CmNsYXNzKGEpCmNsYXNzKGIpCmBgYAoKLS0tLQoKYGBge3J9CmMgPC0gImhlbGxvIgpkIDwtIDEgPiAyCmNsYXNzKGMpCmNsYXNzKGQpCmBgYAoKLS0tLQoKUXVlbGxlIGVzdCBsYSAidGFpbGxlIiBkZSBsJ29iamV0ID8KCmBgYHtyfQpsZW5ndGgoYSkKbGVuZ3RoKGIpCmxlbmd0aChjKQpsZW5ndGgoZCkKYGBgCgotLS0tCgojIyMgTWFuaXB1bGF0aW9ucyBkZSBsJ2Vudmlyb25uZW1lbnQKCmBgYHtyfQpscygpICAgICMjIExlcyB2YXJpYWJsZXMgc2F1dsOpZXMgZGFucyBsJ2Vudmlyb25uZW1lbnQgZGUgdHJhdmFpbApybSh5KSAgICMjIE9uIHJldGlyZSBsYSB2YXJpYWJsZSAieSIKbHMoKQpgYGAKCi0tLS0KCiMjIyBFeGVyY2ljZSAyCgoqIENyw6lleiB1biB2ZWN0ZXVyIGF2ZWMgbGVzIGNpbnEgdmFsZXVycyBzdWl2YW50ZXM6IDUsIDEwLCAxNSwgMjAgZXQgMjUuCiogQ2hlcmNoZXogcXVlbGxlIGZvbmN0aW9uIHBlcm1ldCBkZSBjYWxjdWxlciBsYSBtb3llbm5lICjCq21lYW7CuyBlbiBhbmdsYWlzKSBkJ3VuZSBkaXN0cmlidXRpb24uCiogQ2FsY3VsZXotbGEgOi0pCgotLS0KCiMjIyBMZSBwYWNrYWdlIGByZWFkcmAKCsOAIHBhcnRpciBkZSBsw6AsIG5vdXMgYWxsb25zIHV0aWxpc2VyIGxlIHBhY2thZ2UgYHJlYWRyYCBwb3VyIGxpcmUgZXQgw6ljcmlyZSBkZXMgaW5mb3JtYXRpb25zIHRleHR1ZWxsZXMuCgpMZSBjb2RlIHN1aXZhbnQgdsOpcmlmaWUgc2kgbGUgcGFja2FnZSBlc3QgaW5zdGFsbMOpIHN1ciB2b3RyZSBtYWNoaW5lIGV0IGwnaW5zdGFsbGUgbGUgY2FzIMOpY2jDqWFudC4gUHVpcyBpbCBsZSBjaGFyZ2UuCgpgYGB7cn0KaWYgKCFyZXF1aXJlKHJlYWRyKSkgaW5zdGFsbC5wYWNrYWdlcygicmVhZHIiKQpsaWJyYXJ5KHJlYWRyKQpgYGAKCi0tLS0KCiogTGVzIHBhY2thZ2VzIHNvbnQgZGVzIGVuc2VtYmxlcyBkZSBmb25jdGlvbnMgcXVlIGwnb24gYWpvdXRlIGF1eCBmb25jdGlvbnMgZMOpasOgIGRpc3BvbmlibGVzLgoqIExlcyBmb25jdGlvbnMgZCd1biBwYWNrYWdlIG9udCBlbiBnw6luw6lyYWwgw6l0w6kgcmVncm91cMOpZXMgYXV0b3VyIGQndW5lIHRow6ltYXRpcXVlIGNvbW11bmUuCiogRGFucyBsZSBjYXMgZGUgcmVhZHI6IGwnaW1wb3J0YXRpb24gZXQgbCdleHBvcnRhdGlvbiBkZSBkb25uw6llcyB0ZXh0dWVsbGVzLgoKLS0tLQoKUG91ciBsJ2V4ZXJjaWNlLCBub3VzIGFsbG9ucyB1dGlsaXNlciB1biBqZXUgZGUgZG9ubsOpZXMgYXZlYyB1bmUgbGljZW5jZSBvdXZlcnRlLCB0cm91dsOpIHN1ciBsZSBzaXRlIGRhdGEuZ291di5mcgoKaHR0cHM6Ly93d3cuZGF0YS5nb3V2LmZyL2ZyL2RhdGFzZXRzL2luZGljZXMtbWVuc3VlbHMtZGUtcmV0YXJkLWRlcy1idXMvI18KCi0tLQoKIyMjIGNzdiAoY29tbWEgc2VwYXJhdGVkIHZhbHVlcykKClRvdXQgZCdhYm9yZCwgaWwgZmF1dCBzYXV2ZXIgbGUgZmljaGllciBkYW5zIGxlIGRvc3NpZXIgZGUgdHJhdmFpbCAoIkVOUy1ESC1SIiBkYW5zIG5vdHJlIGNhcykuCgpQdWlzLCBwb3VyIGwnaW1wb3J0ZXIsIGlsIG5lIGZhdXQgcGFzIG91YmxpZXIgbGVzIGd1aWxsZW1ldHMgYXV0b3VyIGR1IG5vbSBkdSBmaWNoaWVyLgoKYGBge3J9CnJldGFyZHMgPC0gcmVhZF9jc3YyKCJpbmRpY2VzLW1lbnN1ZWxzLWRlLXJldGFyZC1kZXMtYnVzLmNzdiIpCmBgYAoKLS0tLQoKSWwgZXN0IGltcG9ydGFudCBkZSBsaXJlIGxlcyBjb21tZW50YWlyZXMgKHNvdXZlbnQgaW5kaXF1w6lzIGVuIHJvdWdlLCBjb21tZSBsZXMgZXJyZXVyc+KApiBtYWxpbikuCgpVbiBhcGVyw6d1IGRlIGNlIHF1ZSBub3VzIGF2b25zIGltcG9ydMOpLgoKYGBge3J9CnJldGFyZHMKYGBgCgotLS0tCgpVbiBhdXRyZSB0eXBlIGQnYXBlcsOndS4KCmBgYHtyfQpzdHIocmV0YXJkcykKYGBgCgotLS0tCgpQb3VyIGNvbnN1bHRlciBsJ29iamV0IGltcG9ydMOpIC0gZGFucyBjZSBjYXMgdW4gdGFibGVhdSBkZSBkb25uw6llcyAtIHZvdXMgcG91dmV6IHZvdXMgcmVuZHJlIGRhbnMgbGEgZmVuw6p0cmUgZW4gaGF1dCDDoCBkcm9pdGUgZGUgUlN0dWRpbyBwdWlzIGRhbnMgbCdvbmdsZXQgIkVudmlyb25tZW50Ii4gVm91cyB0cm91dmVyZXogbGUgdGFibGVhdSBzb3VzICJEYXRhIi4gRW4gZG91YmxlLWNsaXF1YW50IGRlc3N1cywgaWwgcydvdXZyaXJhIGRhbnMgY2V0dGUgZmVuw6p0cmUgZXQgdm91cyBwb3VycmV6IHbDqXJpZmllciBxdWUgbGUgdGFibGVhdSBhIGJpZW4gw6l0w6kgaW1wb3J0w6kuCgotLS0tCgpQb3VyIHNhdXZlciB1biBmaWNoaWVyIGF1IGZvcm1hdCBjc3YsIGlsIGZhdXQgdXRpbGlzZXIgbGEgZm9uY3Rpb24gYHdyaXRlX2NzdmAKCmBgYHtyfQojIyB3cml0ZV9jc3YocmV0YXJkcywgInJldGFyZHMuY3N2IikKYGBgCgotLS0tIAoKIyMjIyB4bHMgKE1pY3Jvc29mdCBFeGNlbC9saWJyZU9mZmljZS9Hb29nbGUgU3ByZWFkc2hlZXRzKQoKKiBPbiBwcsOpZsOocmUgZW4gZ8OpbsOpcmFsIHNhdXZlciBsZXMgZmljaGllcnMgYXUgZm9ybWF0IGNzdiBvdSBqc29uIGNhciBpbHMgc29udCBhaW5zaSBiZWF1Y291cCBwbHVzIGzDqWdlcnMgZXQgbmUgdHJhbnNwb3J0ZW50IHBhcyBsJ2VuY29kYWdlIHBhcmZvaXMgbG91cmQgZCd1biBmaWNoaWVyIHhscy4KKiBOw6lhbm1vaW5zLCBjZSBuJ2VzdCBwYXMgdG91am91cnMgcG9zc2libGUgZCd5IMOpY2hhcHBlciBsb3JzIGQndW5lIGltcG9ydGF0aW9uLgoqIFNpIHBvc3NpYmxlLCBleHBvcnRlciBkZXB1aXMgRXhjZWwgb3UgTGlicmVPZmZpY2Ugdm9zIGRvbm7DqWVzIGF1IGZvcm1hdCBjc3YgKHBvdXIgImNvbW1hIHNlcGFyYXRlZCB2YWx1ZXMiKQoqIFNpbm9u4oCmCgotLS0tCgpgcmVhZHhsYCBlc3QgdW4gcGFja2FnZSBxdWkgcGVybWV0IGQnaW1wb3J0ZXIgKGV0IGQnZXhwb3J0ZXIpIGRlcyBmaWNoaWVycyBhdSBmb3JtYXQgeGxzLgoKYGBge3J9CmlmICghcmVxdWlyZShyZWFkeGwpKSBpbnN0YWxsLnBhY2thZ2VzKCJyZWFkeGwiKQpsaWJyYXJ5KHJlYWR4bCkKYGBgCgotLS0tIAoKUHJlbm9ucyB1biBmaWNoaWVyIGF1IGhhc2FyZCBkZSBsJ09GUyA6ICJDb21wb3J0ZW1lbnQgZGUgbGEgcG9wdWxhdGlvbiBlbiBtYXRpw6hyZSBkZSB0cmFuc3BvcnQsIGNoaWZmcmVzIGNsw6lzIC0gYWdnbG9tw6lyYXRpb24gZGUgTGF1c2FubmUgKGTDqWZpbml0aW9uIDIwMDApIgoKaHR0cHM6Ly93d3cuYmZzLmFkbWluLmNoL2Jmcy9mci9ob21lL3N0YXRpc3RpcXVlcy9jYXRhbG9ndWVzLWJhbnF1ZXMtZG9ubmVlcy90YWJsZWF1eC5hc3NldGRldGFpbC4yMDgyMzUwLmh0bWwKCklsIGVzdCBwcsOpc2VudCBkYW5zIGxlIGRvc3NpZXIuCgotLS0tCgpMYSBmb25jdGlvbiBgcmVhZF9leGNlbGAgZXN0IGNlIHF1J2lsIG5vdXMgZmF1dC4KCmBgYHtyfQphaWUgPC0gcmVhZF9leGNlbCgic3UtZi0xMS4wNC4wMy1NWi0yMDE1LUEyXzU1ODZfRGVmMjAwMC54bHMiKQphaWUKYGBgCgotLS0tCgpUb3V0ZXMgbGVzIGVudHLDqWVzIHNvbnQgY29uc2lkw6lyw6llcyBjb21tZSBkZXMgY2hhw65uZXMgZGUgY2FyYWN0w6hyZXMuCgpgYGB7cn0Kc3RyKGFpZSkKYGBgCgotLS0tCgpMZSByw6lzdWx0YXQgbidlc3QgcGFzIGRpcmVjdGVtZW50IGV4cGxvaXRhYmxlLCBldCBjZSBzZXJhIHNvdXZlbnQgbGUgY2FzIGF1IG1vbWVudCBkZSByw6ljdXDDqXJlciBkZXMgZmljaGllcnMgcHVibGljcywgc291dmVudCBzdHJ1Y3R1csOpcyBwb3VyIGxhIGxlY3R1cmUgZXQgcGFzIHBvdXIgdW5lIGV4cGxvaXRhdGlvbiBhdXRvbWF0aXF1ZS4KCi0tLS0KCkltcG9ydGFudCA6IGxlcyBmb25jdGlvbnMgdXRpbGlzw6llcyB0cmFkaXRpb25uZWxsZW1lbnQgcG91ciBpbXBvcnRlciBkZXMgdGFibGVhdXggKHBhciBleC4gYHJlYWQudGFibGVgKSBpbXBvcnRlbnQgcGFyIGTDqWZhdXQgbGVzIHZhcmlhYmxlcyBhdSBmb3JtYXQgImZhY3RvciIsIHVuIGNvbmNlcHQgc3DDqWNpZmlxdWUgw6AgUiBldCBhdXggbGFuZ2FnZXMgcHLDqXZ1cyBwb3VyIGZhaXJlIGRlcyBzdGF0aXN0aXF1ZXMuIENlIGZvcm1hdCBzJ2VtcGxvaWUgcHJpbmNpcGFsZW1lbnQgbG9yc3F1J3VuZSB2YXJpYWJsZSBlc3QgY2F0w6lnb3JpZWxsZSBvcmRpbmFsZSwgYydlc3Qtw6AtZGlyZSBxdWUgbGVzIHZhbGV1cnMgcXUnZWxsZSBwZXV0IHByZW5kcmUgc29udCBwYXJtaSB1biBlbnNlbWJsZSBkZSAibW90cyIgKGNhdMOpZ29yaWVsbGUpIGV0IHF1J29uIHBldXQgbGVzIGNsYXNzZXIgKG9yZGluYWxlKSwgcGFyIGV4ZW1wbGUgbCdlbnNlbWJsZSA6ICJ0csOocyBtYXV2YWlzIiwgIm1hdXZhaXMiLCAiYm9uIiwgInRyw6hzIGJvbiIuIAoKUG91ciBvYnRlbmlyIGNlIHLDqXN1bHRhdCwgaWwgZmF1dCB1dGlsaXNlciAocXVhbmQgZWxsZSBlc3QgZGlzcG9uaWJsZSkgbCdvcHRpb24gYHN0cmluZ3NBc0ZhY3RvcnMgPSBUUlVFYCBxdWkgZXN0IGfDqW7DqXJhbGVtZW50IHZyYWllICjCq2BUUlVFYMK7KSBwYXIgZMOpZmF1dC4KCi0tLS0KCkxlIG3Dqm1lIGdlbnJlIGRlIHRlY2huaXF1ZSBwZXV0IMOqdHJlIHV0aWxpc8OpIHBvdXIgbGlyZSBkZXMgZmljaGllcnMgYXV4IGZvcm1hdHMgc3VpdmFudHMuCgpidXQgfCBmb25jdGlvbiAKLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tCnB1ciB0ZXh0ZSB8IGByZWFkX2ZpbGVgLCBgcmVhZF9saW5lc2AKcG5nIHwgYHJlYWRQTkdgCnNwc3MgfCBwYWNrYWdlIGBoYXZlbmAKanNvbiB8IHBhY2thZ2UgYGpzb25saXRlYAp4bWwgfCBwYWNrYWdlIGB4bWwyYAoKLS0tLSAKCgojIyMgTGUgdHJhaXRlbWVudCBkZSBkb25uw6llcwoKTGVzIGRhdGEgZnJhbWVzIHNvbnQgdW4gZGVzIGZvcm1hdHMgbGVzIHBsdXMgaW1wb3J0YW50cyBldCBsZXMgcGx1cyBwb3B1bGFpcmVzIGRlIFIuCgpJbCBzJ2FnaXQgZCd1biB0YWJsZWF1IGRlIGRvbm7DqWVzLCBxdSdpbCBuZSBmYXV0IHBhcyBjb25mb25kcmUgYXZlYyBsZXMgZm9ybWF0cyBgbWF0cml4YCBldCBgdGFibGVgLgoKLS0tLQoKVG91cyBsZXMgw6lsw6ltZW50cyBkJ3VuZSBtYXRyaWNlIHNvbnQgZHUgbcOqbWUgdHlwZS4KCmBgYHtyfQptMSA8LSBtYXRyaXgoYygxLCAyLCAzLCA0KSwgbmNvbCA9IDIpCm0xCmBgYAoKLS0tLQoKSWNpIGNlIHNvbnQgdG91cyBkZXMgbm9tYnJlcy4KCmBgYHtyfQpzdHIobTEpCmBgYAoKLS0tLQoKTWFpcyBzaSBvbiBnbGlzc2UgZGVzIGxldHRyZXMgZGFucyBsYSBsaXN0ZeKApgoKYGBge3J9Cm0yIDwtIG1hdHJpeChjKDEsICJiIiwgImMiLCA0KSwgbmNvbCA9IDIpCm0yCmBgYAoKLS0tLQoKTGVzIMOpbMOpbWVudHMgbnVtw6lyaXF1ZXMgc29udCBkZXZlbnVzIGRlcyBjaGHDrm5lcyBkZSBjYXJhY3TDqHJlcy4KCmBgYHtyfQpzdHIobTIpCmBgYAoKLS0tLQoKSWwgc2UgcGFzc2UgcXVlbHF1ZSBjaG9zZSBkJ8OpcXVpdmFsZW50IGF2ZWMgbGVzIHZlY3RldXJzLgoKYGBge3J9CnYxIDwtIGMoMSwgMiwgMykKdjEKYGBgCgotLS0tCgpgYGB7cn0KdjIgPC0gYygxLCAiYiIsIDMpCnYyCmBgYAoKLS0tLQoKRW4gbGVzIGNvbXBhcmFudOKApgoKYGBge3J9CnN0cih2MSkKc3RyKHYyKQpgYGAKCi0tLS0KCkxlIGZvcm1hdCBgdGFibGVgIHF1YW5kIMOgIGx1aSBlc3QgdXRpbGlzw6kgcG91ciBkb25uZXIgZGVzIHRhYmxlcyBkZSBjb250aW5nZW5jZS4KCkRhbnMgY2V0IGV4ZW1wbGUsIG9uIHRpcmUgMjAgZm9pcyB1biBkw6kgNi4KCmBgYHtyfQpkaXN0IDwtIHJvdW5kKHJ1bmlmKDIwLCBtaW4gPSAxLCBtYXggPSA2KSkKZGlzdApgYGAKCi0tLS0KCk9uIHJlZ3JvdXBlIGxlcyByw6lzdWx0YXRzIGxlcyByw6lzdWx0YXRzLgoKYGBge3J9CnRhYmxlKGRpc3QpCmBgYAoKLS0tLQoKVW4gZGF0YSBmcmFtZSBlc3QgdW4gdGFibGVhdSBhdmVjIGRlcyBvYnNlcnZhdGlvbnMgZW4gbGlnbmUgZXQgZGVzIHZhcmlhYmxlcyBlbiBjb2xvbm5lLiBMZXMgdmFyaWFibGVzIHBldXZlbnQgw6p0cmUgZGUgdG91dCB0eXBlIChudW3DqXJpcXVlcywgb3JkaW5hbGVzLCBjYXTDqWdvcmllbGxlcywgZGF0ZXMsIGV0Yy4pLgoKLS0tLQoKIyMjIE1hbmlwdWxlciB1biBkYXRhIGZyYW1lCgpPbiByZXByZW5kIGxlIHRhYmxlYXUgZGUgZG9ubsOpZXMgZGVzIHJldGFyZHMgZGUgYnVzIHZ1IHByw6ljw6lkZW1tZW50LgoKVW4gYXBlcsOndSByYXBpZGUuCgpgYGB7cn0KaGVhZChyZXRhcmRzKQpgYGAKCgotLS0tCgpDb21tZW50IGFjY8OpZGVyIMOgIHVuZSBlbnRyw6llIChsaWduZSkuCgpgYGB7cn0KcmV0YXJkc1sxLF0KYGBgCgotLS0tCgpDb21tZW50IGFjY8OpZGVyIMOgIHVuZSB2YXJpYWJsZS4KCmBgYHtyfQpyZXRhcmRzWywxXSAgICAjIyBsYSBwcmVtacOocmUgdmFyaWFibGUKYGBgCgotLS0tCgpEZSBtYW5pw6hyZSDDqXF1aXZhbGVudGUsIHNpIG9uIGEgbGEgY29ubmFpc3NhbmNlIGRlIGxhZGl0ZSB2YXJpYWJsZS4KCmBgYHtyfQpyZXRhcmRzJE1vaXMKYGBgCgoKLS0tLQoKQ29tbWVudCByw6lhcnJhbmdlciBsZSB0YWJsZWF1IGVuIGZvbmN0aW9uIGRlcyBtb2lzIGRlIGwnYW5uw6llID8KCmBgYHtyfQpyZXRhcmRzIDwtIHJldGFyZHNbb3JkZXIocmV0YXJkcyRNb2lzKSxdCnJldGFyZHMKYGBgCgotLS0tCgpMYSBmb25jdGlvbiBgb3JkZXJgIGFwcGxpcXXDqWUgw6AgYHJldGFyZHMkTW9pc2AgcGVybWV0IGRlIGNsYXNzZXIgbGVzIG1vaXMgZGFucyBsJ29yZHJlIGRvbm7DqSBwYXIgbGVzIGNoYcOubmUgZGUgY2FyYWN0w6hyZSAwMSDDoCAxMS4KCkVuc3VpdGUsIHBsYWPDqSBhdmFudCBsYSB2aXJndWxlLCBgb3JkZXIocmV0YXJkcyRNb2lzKWAgc2lnbmlmaWUgcXVlIG5vdXMgcsOpYXJyYW5nZW9ucyBsJ29yZHJlIGRlcyBsaWduZXMgZHUgdGFibGVhdS4gTCdlZmZldCBzJ2FwcGxpcXVlIHN1ciBsZXMgbGlnbmVzIGVudGnDqHJlcywgZXQgcGFzIHVuaXF1ZW1lbnQgc3VyIGxlcyBjYXNlcyBkZSBsYSBjb2xvbm5lIGNvbmNlcm7DqWUuCgotLS0tCgpOb3VzIGFsbG9ucyB2b2lyIGRhbnMgbGEgcHJvY2hhaW5lIHBhcnRpZSBjb21tZW50IGZhaXJlIGRlcyB2aXN1YWxpc2F0aW9ucyDDoCBwYXJ0aXIgZCd1biB0YWJsZWF1IGRlIGRvbm7DqWVzLgoKLS0tLQoKUG91ciBhbGxlciBwbHVzIGxvaW4sIHVuIHR1dG9yaWFsIGVzdCBkaXNwb25pYmxlIGljaQoKaHR0cDovL3d3dy5jb29rYm9vay1yLmNvbS9NYW5pcHVsYXRpbmdfZGF0YS8KClVuIGF1dHJlIGljaQoKaHR0cDovL3R1dG9yaWFscy5pcS5oYXJ2YXJkLmVkdS9SL1JncmFwaGljcy9SZ3JhcGhpY3MuaHRtbCAKCmV0IGRlcyBleGVyY2ljZXMgaWNpCgpodHRwczovL3d3dy5kYXRhY2FtcC5jb20vY29tbXVuaXR5L3R1dG9yaWFscy8xNS1lYXN5LXNvbHV0aW9ucy1kYXRhLWZyYW1lLXByb2JsZW1zLXIKCi0tLS0KClBvdXIgYXBwcmVuZHJlIMOgIG1hbmlwdWxlciBjb3JyZWN0ZW1lbnQgdW4gb3UgcGx1c2lldXJzIHRhYmxlYXV4IGRlIGRvbm7DqWVzLCBjcsOpZXIgZXQgdHJhbnNmb3JtZXIgZGVzIHZhcmlhYmxlcywgc8OpbGVjdGlvbm5lciBkZXMgc291cy1lbnNlbWJsZXMsIGxlIHBhY2thZ2UgYGRwbHlyYCBlc3QgY2hhdWRlbWVudCByZWNvbW1hbmTDqS4KCmh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9kcGx5ci92aWduZXR0ZXMvZHBseXIuaHRtbAoKLS0tLQoKVW5lIGNoZWF0c2hlZXQgZXN0IMOpZ2FsZW1lbnQgZGlzcG9uaWJsZS4KCmh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL3Jhdy9tYXN0ZXIvZGF0YS10cmFuc2Zvcm1hdGlvbi5wZGYKCi0tLQoKIyMjIExhIHZpc3VhbGlzYXRpb24gZGUgZG9ubsOpZXMKCmBnZ3Bsb3QyYCBtZXQgZW4gcHJhdGlxdWUgKipsYSBncmFtbWFpcmUgZGVzIGdyYXBoaXF1ZXMqKiwgdW5lIHRow6lvcmllIGRlIExlbGFuZCBXaWxraW5zb24gKDE5OTkpLiAKCmh0dHA6Ly93d3cuc3ByaW5nZXIuY29tL3VzL2Jvb2svOTc4MDM4NzI0NTQ0NwoKLS0tLQoKIyMgU2V0dXAuIAoKYGBge3J9CmlmICghcmVxdWlyZShnZ3Bsb3QyKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpCmxpYnJhcnkoZ2dwbG90MikKaWYgKCFyZXF1aXJlKFJDb2xvckJyZXdlcikpIGluc3RhbGwucGFja2FnZXMoIlJDb2xvckJyZXdlciIpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpgYGAKCgojIyBVbiBqZXUgZGUgZG9ubsOpZXMgaW50ZXJuZSDDoCBnZ3Bsb3QyCgpOb3VzIHV0aWxpc29ucyB1biBqZXUgZGUgZG9ubsOpZXMgaW50ZXJuZSDDoCBnZ3Bsb3QyIGNvbnRlbmFudCBsZSBwcml4IGFpbnNpIHF1ZSBkJ2F1dHJlcyBhdHRyaWJ1dHMgZGUgNTMgOTQwIGRpYW1hbnRzLgoKTGVzIGNvbW1hbmRlcyBzdWl2YW50ZXMgcGVybWV0dGVudCBkZSBwcmVuZHJlIGNvbm5haXNzYW5jZSBkdSBqZXUgZGUgZG9ubsOpZXMuCgpgYGB7cn0KIyMgaGVscChkaWFtb25kcykKIyMgVmlldyhkaWFtb25kcykKYGBgCgotLS0tCgpgYGB7cn0KaGVhZChkaWFtb25kcykKYGBgCgotLS0tCgpgYGB7cn0Kc3RyKGRpYW1vbmRzKQpgYGAKCi0tLS0KCk9uIG5lIHZhIHBhcyBnYXJkZXIgbCdlbnRpZXIgZHUgdGFibGVhdSBkZSBkb25uw6llcywgbWFpcyBzZXVsZW1lbnQgdW4gw6ljaGFudGlsbG9uLCBhZmluIGRlIG5lIHBhcyBzdXJjaGFyZ2VyIGNlIG5vdGVib29rLgoKYGBge3J9CmRpYW0gPC0gZGlhbW9uZHNbc2FtcGxlKDE6bnJvdyhkaWFtb25kcyksIDEwMDApLF0KYGBgCgojIyBDaGFyZ2VyIGxlcyBkb25uw6llcyBkYW5zIGdncGxvdAoKKipSZW1hcnF1ZSoqIDogw6AgcGFydGlyIGQnaWNpLCBub3VzIHBhcmxvbnMgcHJpbmNpcGFsZW1lbnQgZGUgZ2dwbG90IHNhbnMgbGUgIjIiIGNhciBpbCB2YSBzJ2FnaXIgZGUgbGEgZm9uY3Rpb24gImdncGxvdCIuIAoKTGUgIjIiIG4nZXN0IHV0aWxpc8OpIHF1ZSBkYW5zIGxlIG5vbSBkdSBwYWNrYWdlLiAKCmdncGxvdDEgW2V4aXN0ZV0oaHR0cHM6Ly9naXRodWIuY29tL2hhZGxleS9nZ3Bsb3QxKS4KCi0tLS0KCiMjIyBPbiBjaGFyZ2UgbGVzIGRvbm7DqWVzCgoqQXR0ZW50aW9uICEgTCdpbnB1dCBkb2l0IHRvdWpvdXJzIMOqdHJlIHVuIGRhdGEgZnJhbWUgISoKCmBgYHtyfQpnIDwtIGdncGxvdChkaWFtKQpgYGAKCi0tLS0KCmBgYHtyfQpnCmBgYAoKLS0tLQoKQ29udHJhaXJlbWVudCDDoCBsYSBmb25jdGlvbiBgcGxvdGAsIHF1aSBicmljb2xlIHVuIHZpc3VlbCBsb3JzcXUnb24gZW50cmUgbGEgY29tbWFuZGUgYHBsb3QoZGlhbW9uZHMpYCwgYXZlYyBgZ2dwbG90YCBpbCBuZSBzZSBwYXNzZSByaWVuLgoKSnVzdGUgdW4gZ3JhbmQgY2FycsOpIGdyaXMuCgotLS0tCgpMZSByw6lzdWx0YXQgYXZlYyBsZSBwYWNrYWdlIGBiYXNlYC4KCmBgYHtyfQojIyBwbG90KGRpYW1vbmRzKQpgYGAKCgotLS0tCgpMYSBjb21tYW5kZSBkZSBiYXNlIGBwbG90KGRpYW1vbmRzKWAgY29uc2lkw6hyZSB0b3V0ZXMgbGVzIHZhcmlhYmxlcyBkdSBkYXRhIGZyYW1lIGV0IGxlcyBjcm9pc2UgZW50cmUgZWxsZXMgLSBudW3DqXJpcXVlcyBjb21tZSBvcmRpbmFsZXMgLSBwb3VyIHVuIHLDqXN1bHRhdCBpbGxpc2libGUgZXQgaW51dGlsZS4KClF1J29idGllbnQtb24gYXZlYyBsYSBjb21tYW5kZSBgZ2dwbG90YCA/CgotLS0tCgpgYGB7cn0Kc3RyKGcpCmBgYAoKLS0tLQoKT24gcmV0cm91dmUgbGUgZGF0YSBmcmFtZSBjb250ZW5hbnQgbm9zIGRvbm7DqWVzLCBhaW5zaSBxdWUgbGVzIGRpZmbDqXJlbnRzIMOpbMOpbWVudHMgcHJvcHJlcyDDoCBsJ2FwcHJvY2hlICpncmFtbWF0aWNhbGUqIGRlcyBncmFwaGlxdWVzIHByb3Bvc8OpZSBwYXIgYGdncGxvdDJgLCBwb3VyIGwnaW5zdGFudCB2aWRlcyA6CgoqIGxheWVycwoKKiBzY2FsZXMKCiogbWFwcGluZwoKKiB0aGVtZQoKKiBjb29yZGluYXRlcwoKKiBmYWNldAoKLS0tLQoKw4AgY2Ugc3RhZGUsIGxlIGdyYXBoZSBlc3QgdmlkZSBjYXIgbm91cyBuJ2F2b25zIGTDqWZpbmkgbmkgbWFwcGluZyBuaSBnw6lvbcOpdHJpZXMgKHZvaXIgZmlndXJlIHN1aXZhbnRlKS4KCi0tLS0KCmBgYHtyfQpnIDwtIGdncGxvdChkaWFtKQpnCmBgYAoKIyMgTWFwcGluZwoKIyMjIEF2ZWMgdW4gbWFwcGluZyBtYWlzIHNhbnMgZ8Opb23DqXRyaWUKCmNmLiBmaWd1cmUgc3VpdmFudGUuCgpQb3VyIHJhcHBlbCA6ICdjYXJhdCcgZXQgJ3ByaWNlJyBzb250IGRlcyB2YXJpYWJsZXMgY29udGludWVzLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSAKYGBgCgotLS0tCgpPbiByZW1hcnF1ZSBxdWUgbGVzIMOpY2hlbGxlcyBldCBsZXMgbGFiZWxzIGRlcyBheGVzIHNvbnQgZMOpasOgIHBvc8Opcy4gCgpgZ2dwbG90YCBhdHRlbmQgbWFpbnRlbmFudCBkZSBzYXZvaXIgcXVvaSBkZXNzaW5lci4KCi0tLS0KCiMjIyBBdmVjIHVuZSBnw6lvbcOpdHJpZSBtYWlzIHNhbnMgbWFwcGluZwoKUGFzIGRlIGZpZ3VyZS4uLgoKYGBge3IsIGVycm9yID0gVFJVRX0KZ2dwbG90KGRpYW0pICsgZ2VvbV9wb2ludCgpCmBgYAoKLS0tLQoKQ2V0dGUgZm9pcywgZ2dwbG90IG4nYSBwYXMgdHJvdXbDqSBkZSBtYXBwaW5nIGx1aSBpbmRpcXVhbnQgb8O5IHBvc2VyIHNvbiBkZXNzaW4sIGQnb8O5IGwnZXJyZXVyLgoKLS0tLQoKIyMjIEF2ZWMgdW4gbWFwcGluZyBldCB1bmUgZ8Opb23DqXRyaWUKCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkgKyBnZW9tX3BvaW50KCkKYGBgCgotLS0tCgpQb3VyIGNvbXBhcmFpc29uLCBsYSBjb21tYW5kZSBsYSBwbHVzIHNpbXBsZSBwZXJtZXR0YW50IGQnb2J0ZW5pciAow6AgcGV1IHByw6hzKSBsZSBtw6ptZSByw6lzdWx0YXQgYXZlYyBsZSBwYWNrYWdlIGRlIGJhc2UuCgpgYGB7cn0KcGxvdChkaWFtJGNhcmF0LCBkaWFtJHByaWNlKQpgYGAKCi0tLS0KCkxlcyDDqWNoZWxsZXMgc29udCBqdXN0ZXMgZXQgbGVzIHBvaW50cyBzb250IGJpZW4gc2l0dcOpcywgbWFpcyBjJ2VzdCB0b3V0IGV0IGMnZXN0IG1vY2hlLgoKLS0tLQoKQydlc3QgbMOgIGxlIHByaW5jaXBlIGRlIGdncGxvdCA6IMOgIHBhcnRpciBkZSBtYWludGVuYW50IG5vdXMgcG91dm9ucyBmYWlyZSB2YXJpZXIgbGVzIMOpbMOpbWVudHMgZ3JhcGhpcXVlcyBzYW5zIGF2b2lyIMOgIHRvdWNoZXIgYXV4IGRvbm7DqWVzLgoKUGFyIGV4ZW1wbGUsIHVuZSBpbnRlcnBvbGF0aW9uLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSArIGdlb21fc21vb3RoKCkKYGBgCgojIyBTdXBlcnBvc2VyIGRlcyBsYXllcnMKCklsIHN1ZmZpdCBkZSBsZXMgYWRkaXRpb25uZXIgcG91ciBsZXMgc3VwZXJwb3Nlci4KCioqQXR0ZW50aW9uKiogYXUgJysnIMOgIG1ldHRyZSDDoCBsYSBmaW4gZGUgbGEgbGlnbmUgZXQgcGFzIGF1IGTDqWJ1dC4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBnZW9tX3Ntb290aCgpCmBgYAoKLS0tLQoKQXR0ZW50aW9uLCBsJ29yZHJlIGRlcyBnw6lvbcOpdHJpZXMgYSB1bmUgaW5mbHVlbmNlIHN1ciBsZSBncmFwaGlxdWUgIQoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSArIAogIGdlb21fc21vb3RoKCkgKyAKICBnZW9tX3BvaW50KCkKYGBgCgotLS0tCgpEYW5zIGxlIGdyYXBoaXF1ZSBwcsOpY8OpZGVudCwgbGEgY291cmJlIGQnaW50ZXJwb2xhdGlvbiBhIMOpdMOpIGRlc3NpbsOpZSBhdmFudCBsZXMgcG9pbnRzLgoKQWluc2ksIGVsbGUgYXBwYXJhw650IGRhbnMgbGEgZmlndXJlIGNhY2jDqWUgc291cyBjZXMgZGVybmllcnMuCgojIyBEaWZmw6lyZW50cyBtYXBwaW5ncwoKTGUgbWFwcGluZyBwZXV0IHNlIGZhaXJlIMOgIHBsdXNpZXVycyBlbmRyb2l0cyA6IAoKKiBEYW5zIGBnZ3Bsb3QoKWAsIGNlIHF1aSBhIHBvdXIgZWZmZXQgZCdhcHBsaXF1ZXIgbGUgbWFwcGluZyDDoCB0b3VzIGxlcyBhdXRyZXMgw6lsw6ltZW50cy4KCiogRGFucyBsZXMgw6lsw6ltZW50cyBncmFwaGlxdWVzIGV1eC1tw6ptZXMuCgotLS0tCgojIyMgVG91dCBkYW5zIGdncGxvdAoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSArIGdlb21fcG9pbnQoKQpgYGAKCi0tLS0KCiMjIyBEYW5zIGxlcyBkZXV4CgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQpKSArIGdlb21fcG9pbnQoYWVzKHkgPSBwcmljZSkpCmBgYAoKLS0tLQoKIyMjIFRvdXQgZGFucyBsYSBnw6lvbcOpdHJpZQoKYGBge3J9CmdncGxvdChkaWFtKSArIGdlb21fcG9pbnQoYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkKYGBgCgotLS0tCgpCcmVmLCBvbiBwZXV0IG1hcHBlciBkZSBub21icmV1c2VzIHZhcmlhYmxlcywgZW4gdW5lIHNldWxlIGZvaXMsIGRpcmVjdGVtZW50IGRhbnMgYGdncGxvdGAgZXQgZWxsZXMgc2Vyb250IHJlcHJpc2VzIHBhciBsZXMgYXV0cmVzIMOpbMOpbWVudHMuIAoKUGFyIGV4ZW1wbGUsIGwnYXR0cmlidXQgYGNvbG9yYCBkZSBgZ2VvbV9wb2ludGAuIAoKKlJlbWFycXVlKiA6IGxhIHZhcmlhYmxlICdjbGFyaXR5JyBlc3Qgb3JkaW5hbGUuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkKYGBgCgotLS0tCgpHcsOiY2Ugw6AgY2Ugc2V1bCBtYXBwaW5nLCBgZ2dwbG90YCBhdHRyaWJ1ZSDDoCBjaGFxdWUgbW9kYWxpdMOpIHVuZSBjb3VsZXVyLCBhdHRyaWJ1ZSBhdXggcG9pbnRzIGxhIGNvdWxldXIgY29ycmVzcG9uZGFudGUsIGV0IGfDqW7DqHJlIGRhbnMgbGEgZm91bMOpZSB1bmUgbMOpZ2VuZGUgKHVuZSBkZXMgYW5nb2lzc2VzIGxvcnNxdSdvbiB0cmF2YWlsbGUgYXZlYyBsZXMgY29tbWFuZGVzIGRlIGJhc2UpLgoKUG91ciBsJ2FuZWNkb3RlLCBub3VzIGF1cmlvbnMgb2J0ZW51IGxlIG3Dqm1lIHLDqXN1bHRhdCBlbiBmYWlzYW50IGxlIG1hcHBpbmcgZGFucyBgZ2VvbV9wb2ludGAuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpICsgCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjbGFyaXR5KSkKYGBgCgotLS0tCgpBdHRlbnRpb24gw6AgbmUgcGFzIG91YmxpZXIgZGUgZmFpcmUgbGUgbWFwcGluZywgYydlc3Qtw6AtZGlyZSBkJ3V0aWxpc2VyIGxhIGZvbmN0aW9uIGBhZXMoKWAsIHNpbm9uIMOnYSBuZSBmb25jdGlvbm5lcmEgcGFzICEKCmBgYHtyLCBlcnJvciA9IFRSVUV9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSArIAogIGdlb21fcG9pbnQoY29sb3IgPSBjbGFyaXR5KQpgYGAKCi0tLS0KCk9uIHRyb3V2ZSB0b3V0ZXMgbGVzIGfDqW9tw6l0cmllcyBkaXNwb25pYmxlcyBhaW5zaSBxdWUgZGUgbm9tYnJldXNlcyBhdXRyZXMgcmVzc291cmNlcyBpbmRpc3BlbnNhYmxlcyBkYW5zIGwnKippbmRpc3BlbnNhYmxlKiogW2NoZWF0IHNoZWV0IGRlIGdncGxvdDJdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE1LzAzL2dncGxvdDItY2hlYXRzaGVldC5wZGYpICEKCi0tLS0KCiMjIyBWYXJpYWJsZXMgZXQgdHJhbnNmb3JtYXRpb25zLCBkaXNjcsOodGVzIGV0IGNvbnRpbnVlcwoKQydlc3QgaGFzYXJkZXV4LCBtYWlzIG9uIHBldXQgw6lnYWxlbWVudCBhcHBsaXF1ZXIgdW5lIHRyYW5zZm9ybWF0aW9uIGNvbnRpbnVlIChgc2l6ZWApIMOgIHVuZSB2YXJpYWJsZSBvcmRpbmFsZSAoYGN1dGApLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSwgc2l6ZSA9IGN1dCkpICsgCiAgZ2VvbV9wb2ludCgpCmBgYAoKLS0tLQoKVG91dGVmb2lzLCBpbCBlc3QgcmVjb21tYW5kw6kgZCdhcHBsaXF1ZXIgdW5lIHRyYW5zZm9ybWF0aW9uIGNvbnRpbnVlIChgc2l6ZWApIMOgIHVuZSB2YXJpYWJsZSBjb250aW51ZSAocGFyIGV4ZW1wbGUgYGRlcHRoYCkgZXQgdW5lIHRyYW5zZm9ybWF0aW9uIGRpc2Nyw6h0ZSBjb21tZSBsYSBjb3VsZXVyIG91IGxhIGZvcm1lIChgc2hhcGVgKSDDoCB1bmUgdmFyaWFibGUgZGlzY3LDqHRlIChwYXIgZXhlbXBsZSBgY3V0YCkuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5LCBzaGFwZSA9IGN1dCkpICsgCiAgZ2VvbV9wb2ludCgpCmBgYAoKLS0tLQoKRMOpdGFpbCBxdWkgYSBzb24gaW1wb3J0YW5jZSBkYW5zIGwnZXhlbXBsZSBzdWl2YW50IDogbGEgZm9uY3Rpb24gYGdlb21fc21vb3RoYCB2YSBow6lyaXRlciBkdSBtYXBwaW5nIHN1ciBsYSBjb3VsZXVyLgoKKk5vdGUqIDogInNlID0gRkFMU0UiIGVtcMOqY2hlIGwnYWZmaWNoYWdlIGRlIGwnaW5jZXJ0aXR1ZGUuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKQpgYGAKCi0tLS0KClBvdXIgbGUgbWFwcGluZyBzdXIgbGEgZm9ybWUgKGF1IGxpZXUgZGUgbGEgY291bGV1ciksIG9uIHJlcGFzc2VyYS4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBzaGFwZSA9IGN1dCkpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkKYGBgCgotLS0tCgpFdCBzaSBsJ29uIHByZW5kIGVuIGNvbnNpZMOpcmF0aW9uIGxlcyBkZXV4IGVuIG3Dqm1lIHRlbXBzLCDDp2EgcGV1dCBtZW5lciDDoCBsYSBjYXRhc3Ryb3BoZS4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHksIHNoYXBlID0gY3V0KSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKQpgYGAKCi0tLS0KCkxlIG3Dqm1lIGNvZGUgcXVlIHByw6ljw6lkZW1tZW50LCBhdmVjIGNldHRlIGZvaXMgbGUgZ3JhcGhpcXVlIMOgIGxhIHBsYWNlIGR1IG1lc3NhZ2UgZCdlcnJldXIuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHksIHNoYXBlID0gY3V0KSkgKyAKICBnZW9tX3BvaW50KCkgKyBnZW9tX3Ntb290aChzZSA9IEZBTFNFKQpgYGAKCi0tLS0KCklsIGVzdCBuw6ljZXNzYWlyZSBkZSByZWRpc3RyaWJ1ZXIgbGUgbWFwcGluZyBwbHVzIHN1YnRpbGVtZW50LgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSkpICsgCiAgZ2VvbV9wb2ludChhZXMoc2hhcGUgPSBjdXQpKSArIAogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpCmBgYAoKLS0tLQoKRCdhdXRyZXMgZXhlbXBsZXMgY29uY2VybmFudCBsZXMgYXR0cmlidXRzIGRlcyDDqWzDqW1lbnRzIGfDqW9tw6l0cmlxdWVzLgoKLS0tLQoKIyMjIFRyYW5zZm9ybWF0aW9uIGNvbnRpbnVlLCB2YXJpYWJsZSBjb250aW51ZQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSwgc2l6ZSA9IGRlcHRoKSkgKyAKICBnZW9tX3BvaW50KCkKYGBgCgotLS0tCgpJbCBlc3QgcG9zc2libGUgw6l2aWRlbW1lbnQgZGUgbW9kaWZpZXIgcGx1cyBzdWJ0aWxlbWVudCBsYSB0YWlsbGUgZGVzIHNvbW1ldHMgbG9yc3F1J29uIGZhaXQgdW4gbWFwcGluZyBkZXNzdXMsIHNpIGxlcyB2YWxldXJzIHBhciBkw6lmYXV0IG5lIG5vdXMgcGxhaXNlbnQgcGFzLiAKCkVuIGfDqW7DqXJhbCwgY2VsYSBwYXNzZSBwYXIgbGVzIGZvbmN0aW9ucyBgc2NhbGVgLiAKCkVsbGVzIGNvbW1lbmNlbnQgcGFyIGBzY2FsZV9gICh2b2lyIGxhIGNoZWF0IHNoZWV0KS4gCgpOb3VzIHJldmllbmRyb25zIHBsdXMgZW4gZMOpdGFpbCBsw6AtZGVzc3VzLgoKRGFucyBsYSBmaWd1cmUgc3VpdmFudGUsIG5vdXMgZG9ubm9ucyBkZXMgdmFsZXVycyBtaW5pbWFsZXMgZXQgbWF4aW1hbGVzIHBvdXIgbGEgdGFpbGxlIGRlcyBzb21tZXRzLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSwgc2l6ZSA9IGRlcHRoKSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBzY2FsZV9zaXplKHJhbmdlID0gYygxLDMpKQpgYGAKCi0tLS0KCipSZW1hcnF1ZSogOiBsJ2VmZmV0IGVzdCBkaWZmaWNpbGUgw6Agb2JzZXJ2ZXIgY2FyIGxhIHZhcmlhbmNlIGVzdCB0csOocyBwZXRpdGUuCgpgYGB7cn0Kc3VtbWFyeShkaWFtJGRlcHRoKQpzZChkaWFtJGRlcHRoKQpgYGAKCi0tLS0KCkF1IHBhc3NhZ2UsIHJlbWFycXVvbnMgcXVlIGwnb24gcGV1dCBhdXNzaSB1dGlsaXNlciBsJ2F0dHJpYnV0IGBzaXplYCBkZSBgZ2VvbV9wb2ludGAgc2FucyBtYXBwaW5nLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSkKYGBgCgotLS0tCgpEYW5zIGNlIGNhcywgbGEgdGFpbGxlIGRlcyBwb2ludHMgZXN0IGNvbnNpZMOpcsOpZSBob3JzIG1hcHBpbmcgZXQgZG9uYyBpbmTDqXBlbmRhbW1lbnQgZCd1bmUgcXVlbGNvbnF1ZSB2YXJpYWJsZS4KCi0tLS0KClJlbWFycXVvbnMgw6lnYWxlbWVudCBsJ29yZ2FuaXNhdGlvbiBoacOpcmFyY2hpcXVlIGRlcyBtYXBwaW5ncyBldCBkZXMgdHJhbnNmb3JtYXRpb25zIDogZGFucyBsJ2V4ZW1wbGUgcXVpIHN1aXQsIGxhIHZhcmlhYmxlICBgZGVwdGhgIGVzdCB0b3V0IGQnYWJvcmQgbWFwcMOpZSBzdXIgbGEgdGFpbGxlIGRlcyBzb21tZXRzLiAKClB1aXMsIGRhbnMgYGdlb21fcG9pbnRgLCBvbiBsdWkgYXR0cmlidWUgdW5lIHZhbGV1ciBmaXhlLiAKCkFycml2w6llIGVuc3VpdGUsIGMnZXN0IGNldHRlIGRlcm5pw6hyZSBxdWkgbCdlbXBvcnRlIHN1ciBsZSBtYXBwaW5nIGluaXRpYWwuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5LCBzaXplID0gZGVwdGgpKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEpCmBgYAoKLS0tLQoKQXUgcGFzc2FnZSwgbGEgbW9kaWZpY2F0aW9uIGR1IHRpdHJlIGVuIGzDqWdlbmRlIGTDqXBlbmQgZGUgbGEgZm9uY3Rpb24gYHNjYWxlYCBjb3JyZXNwb25kYW50ZSAoaWNpIGBzY2FsZV9zaXplYCkuCgpDJ2VzdCBsb2dpcXVlLCBtYWlzIGNvbnRyZS1pbnR1aXRpZiBwb3VyIHF1aSBhdXJhIHBhc3PDqSBiZWF1Y291cCAodHJvcCA/KSBkZSB0ZW1wcyBhdmVjIGxlcyBjb21tYW5kZXMgZ3JhcGhpcXVlcyBkZSBiYXNlIGRhbnMgUi4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHksIHNpemUgPSBkZXB0aCkpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgc2NhbGVfc2l6ZSgiREVQVEgiLCByYW5nZSA9IGMoMSwzKSkKYGBgCgojIyBCaWxhbiBpbnRlcm3DqWRpYWlyZQoKTm91cyBhdm9ucyB2dSBjb21tZW50IDoKCiogQ2hhcmdlciBsZXMgZG9ubsOpZXMuCgoqIEZhaXJlIHVuIG1hcHBpbmcuCgoqIFN1cGVycG9zZXIgdW5lIG91IHBsdXNpZXVycyBnw6lvbcOpdHJpZXMuCgotLS0tCgpJbCBub3VzIHJlc3RlIMOgIGTDqWNvdXZyaXIgOgoKKiBMZSBmYWNldHRhZ2UuCgoqIExlcyDDqWNoZWxsZXMuCgoqIExlcyBhbm5vdGF0aW9ucy4KCiogTGVzIHR5cGVzIGRlIGdyYXBoaXF1ZXMgYXV0cmVzIHF1ZSBsZXMgKnNjYXR0ZXJwbG90cyouCgoqIENvbW1lbnQgc2F1dmVyIHVuIGdyYXBoaXF1ZS4KCiogQ29tbWVudCBiaWVuIHByw6lwYXJlciBzZXMgZG9ubsOpZXMuCgojIyBMZSBmYWNldHRhZ2UKCkF0dGVudGlvbiAhIENlY2kgcydhcHBsaXF1ZSDDoCBkZXMgdmFyaWFibGVzIGRpc2Nyw6h0ZXMuCgpMZSBmYWNldHRhZ2UgZGl2aXNlIGxlIGpldSBkZSBkb25uw6llcyBlbiBmb25jdGlvbiBkZXMgY2F0w6lnb3JpZXMgZCd1bmUgdmFyaWFibGUuCgpEYW5zIHVuIHNlbnPigKYgCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpICsgCiAgZ2VvbV9wb2ludCgpICsKICBmYWNldF9ncmlkKC4gfiBjdXQpIApgYGAKCi0tLS0KCuKApiBldCBkYW5zIGwnYXV0cmUuCgooUmVtYXJxdWV6IGxhIHBvc2l0aW9uIGludmVyc8OpZSBkZSBsYSB2YXJpYWJsZSBgY3V0YCBkYW5zIGBmYWNldF9ncmlkKClgLikKCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGZhY2V0X2dyaWQoY3V0IH4gLikgCmBgYAoKLS0tLQoKRW4gY3JvaXNhbnQgZGV1eCB2YXJpYWJsZXMgZGlzY3LDqHRlcy4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGZhY2V0X2dyaWQoY29sb3IgfiBjbGFyaXR5KSAKYGBgCgotLS0tCgpGaW5hbGVtZW50LCBlbiBjcm9pc2FudCBkZXV4IHZhcmlhYmxlcyBkaXNjcsOodGVzLCBhdmVjIHVuIG1hcHBpbmcgc3VyIGxhIGNvdWxldXIuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjdXQpKSArIAogIGdlb21fcG9pbnQoKSArCiAgZmFjZXRfZ3JpZChjb2xvciB+IGNsYXJpdHkpIApgYGAKCi0tLS0KCklsIHkgYSB1bmUgYXV0cmUgb3B0aW9uIGRlIGZhY2V0dGFnZSBsb3JzcXUnb24gbid1dGlsaXNlIHF1J3VuZSBzZXVsZSB2YXJpYWJsZSBkaXNjcsOodGUgOiBgZmFjZXRfd3JhcGAuCgpEYW5zIGNlIGNhcywgcmVtYXJxdWV6IHF1ZSBub3VzIG4ndXRpbGlzb25zIHBsdXMgbGUgcG9pbnQgKC4pIGF2YW50IGxlIHRpbGRlICh+KS4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGZhY2V0X3dyYXAofiBjbGFyaXR5KSAKYGBgCgoKIyMgTGVzIMOpY2hlbGxlcwoKRWxsZXMgY29tbWVuY2VudCB0b3V0ZXMgcGFyIGBzY2FsZV9gCgpFbnN1aXRlLCBvbiBjb21wbMOodGUgYXZlYyBsZSBub20gZGUgbGEgdmFyaWFibGUgY29uY2VybsOpZS4KCi0tLS0KCgpzY2FsZV9hbHBoYSBzY2FsZV9hbHBoYV9jb250aW51b3VzIHNjYWxlX2FscGhhX2Rpc2NyZXRlIHNjYWxlX2FscGhhX2lkZW50aXR5IHNjYWxlX2FscGhhX21hbnVhbCBzY2FsZV9jb2xvcl9icmV3ZXIgc2NhbGVfY29sb3JfY29udGludW91cyBzY2FsZV9jb2xvcl9kaXNjcmV0ZSBzY2FsZV9jb2xvcl9kaXN0aWxsZXIgc2NhbGVfY29sb3JfZ3JhZGllbnQgc2NhbGVfY29sb3JfZ3JhZGllbnQyIHNjYWxlX2NvbG9yX2dyYWRpZW50biBzY2FsZV9jb2xvcl9ncmV5IHNjYWxlX2NvbG9yX2h1ZSBzY2FsZV9jb2xvcl9pZGVudGl0eSBzY2FsZV9jb2xvcl9tYW51YWwgc2NhbGVfY29sb3VyX2JyZXdlciBzY2FsZV9jb2xvdXJfY29udGludW91cyBzY2FsZV9jb2xvdXJfZGF0ZSBzY2FsZV9jb2xvdXJfZGF0ZXRpbWUgc2NhbGVfY29sb3VyX2Rpc2NyZXRlIHNjYWxlX2NvbG91cl9kaXN0aWxsZXIgc2NhbGVfY29sb3VyX2dyYWRpZW50IHNjYWxlX2NvbG91cl9ncmFkaWVudDIgc2NhbGVfY29sb3VyX2dyYWRpZW50biBzY2FsZV9jb2xvdXJfZ3JleSBzY2FsZV9jb2xvdXJfaHVlIHNjYWxlX2NvbG91cl9pZGVudGl0eSBzY2FsZV9jb2xvdXJfbWFudWFsIHNjYWxlX2NvbnRpbnVvdXMgc2NhbGVfZGF0ZSBzY2FsZV9maWxsX2JyZXdlciBzY2FsZV9maWxsX2NvbnRpbnVvdXMgc2NhbGVfZmlsbF9kYXRlIHNjYWxlX2ZpbGxfZGF0ZXRpbWUgc2NhbGVfZmlsbF9kaXNjcmV0ZSBzY2FsZV9maWxsX2Rpc3RpbGxlciBzY2FsZV9maWxsX2dyYWRpZW50IHNjYWxlX2ZpbGxfZ3JhZGllbnQyIHNjYWxlX2ZpbGxfZ3JhZGllbnRuIHNjYWxlX2ZpbGxfZ3JleSBzY2FsZV9maWxsX2h1ZSBzY2FsZV9maWxsX2lkZW50aXR5IHNjYWxlX2ZpbGxfbWFudWFsIHNjYWxlX2lkZW50aXR5IHNjYWxlX2xpbmV0eXBlIHNjYWxlX2xpbmV0eXBlX2NvbnRpbnVvdXMgc2NhbGVfbGluZXR5cGVfZGlzY3JldGUgc2NhbGVfbGluZXR5cGVfaWRlbnRpdHkgc2NhbGVfbGluZXR5cGVfbWFudWFsIHNjYWxlX21hbnVhbCBzY2FsZV9yYWRpdXMgc2NhbGVfc2hhcGUgc2NhbGVfc2hhcGVfY29udGludW91cyBzY2FsZV9zaGFwZV9kaXNjcmV0ZSBzY2FsZV9zaGFwZV9pZGVudGl0eSBzY2FsZV9zaGFwZV9tYW51YWwgc2NhbGVfc2l6ZSBzY2FsZV9zaXplX2FyZWEgc2NhbGVfc2l6ZV9jb250aW51b3VzIHNjYWxlX3NpemVfZGF0ZSBzY2FsZV9zaXplX2RhdGV0aW1lIHNjYWxlX3NpemVfZGlzY3JldGUgc2NhbGVfc2l6ZV9pZGVudGl0eSBzY2FsZV9zaXplX21hbnVhbCBzY2FsZV94X2NvbnRpbnVvdXMgc2NhbGVfeF9kYXRlIHNjYWxlX3hfZGF0ZXRpbWUgc2NhbGVfeF9kaXNjcmV0ZSBzY2FsZV94X2xvZzEwIHNjYWxlX3hfcmV2ZXJzZSBzY2FsZV94X3NxcnQgc2NhbGVfeV9jb250aW51b3VzIHNjYWxlX3lfZGF0ZSBzY2FsZV95X2RhdGV0aW1lIHNjYWxlX3lfZGlzY3JldGUgc2NhbGVfeV9sb2cxMAlzY2FsZV95X3JldmVyc2Ugc2NhbGVfeV9zcXJ0CgotLS0tCgpQYXIgZXhlbXBsZSwgc2kgbCdvbiB0cmF2YWlsbGUgc3VyIGxhIGNvdWxldXIsIG9uIHBvdXJyYSBmYWlyZSB2YXJpZXIgbGEgcGFsZXR0ZSBkZXMgY291bGV1cnMgZW4gbW9kaWZpYW50IGxlIG5vbSBkZSBsJ8OpY2hlbGxlLiBGYWl0ZXMgbGUgdGVzdCBlbiDDqWNyaXZhbnQgYHNjYWxlX2NvbG9yX2AgZGFucyBsYSBjb25zb2xlIHB1aXMgZW4gcHJlc3NhbnQgc3VyIGxhIHRvdWNoZSB0YWIgcG91ciB2b2lyIGxlcyBzdWdnZXN0aW9uc+KApgoKLS0tLQoKRW4gZ3JpcyAKCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3JfZ3JleSgpCmBgYAoKLS0tLQoKTGEgdmVyc2lvbiBwYXIgZMOpZmF1dC4KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3JfZGlzY3JldGUoKQpgYGAKCi0tLS0KCkNvbG9yQnJld2VyLCBub21tw6llIGQnYXByw6hzIHVuZSBkZSBzZXMgYXV0ZXVycywgQ255dGhpYSBCcmV3ZXIsIGVzdCB1bmUgbGlicmFpcmllIGRlIGNvdWxldXJzIHByw6ljYWxjdWzDqWVzIHF1aSBzJ2FjY29yZGVudCBiaWVuLgoKT24gcGV1dCBsZXMgdXRpbGlzZXIgaWNpIGF2ZWMgbGEgZm9uY3Rpb24gYHNjYWxlX2NvbG9yX2JyZXdlcmAuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcigpCmBgYAoKLS0tLQoKRW4gY2hhbmdlYW50IGRlIHBhbGV0dGUuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gMikKYGBgCgotLS0tCgpBdHRlbnRpb24gc2kgdm91cyB1dGlsaXNleiBsYSBtYXV2YWlzZSDDqWNoZWxsZSA6IHNvaXQgaWwgbmUgc2UgcGFzc2VyYSByaWVuIChjb21tZSBkYW5zIGxhIGZpZ3VyZSBzdWl2YW50ZSksIHNvaXQgaWwgeSBhdXJhIHVuIG1lc3NhZ2UgZCdlcnJldXIuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAyKQpgYGAKCi0tLS0KCkRhbnMgbGEgZmlndXJlIHN1aXZhbnRlLCBub3VzIGNoYW5nZW9ucyBsYSBmb3JtZSB1dGlsaXPDqWUgcG91ciBkZXNzaW5lciBsZXMgcG9pbnRzIGFmaW4gcXUnaWwgeSBhaXQgdW4gcG91cnRvdXIgKGBjb2xvcmApIGV0IHVuIGNvbnRlbnUgKGBmaWxsYCkuCgpDZXR0ZSB0cmFuc2Zvcm1hdGlvbiBhIMOpdMOpIGVmZmVjdHXDqWUgZW4gZG9ubmFudCBjb21tZSBpbnN0cnVjdGlvbiBxdWUgbGVzIHBvaW50cyBkb2l2ZW50IGNoYW5nZXIgZGUgZm9ybWUgKGluZMOpcGVuZGFtbWVudCBkZSB0b3V0ZSB2YXJpYWJsZSkuCgpOb3VzIGVuIHByb2ZpdG9ucyBwb3VyIGRlc3NpbmVyIGwnaW50w6lyaWV1ci4gUXVlbGxlIGZvbmN0aW9uIGZhdXQtaWwgdXRpbGlzZXIgPwoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGZpbGwgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KHNoYXBlID0gMjEpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gMikKYGBgCgotLS0tCgpNYWlzIGxlcyDDqWNoZWxsZXMsIMOnYSBuZSBjb25jZXJuZSBwYXMgc2V1bGVtZW50IGwnaW50w6lyaWV1ciBkdSBncmFwaGlxdWUuCgpOb3VzIHV0aWxpc29ucyDDqWdhbGVtZW50IGRlcyDDqWNoZWxsZXMgc3VyIGxlcyBheGVzLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSkpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGMoMSwzKSwgbWlub3JfYnJlYWtzID0gYyhzcXJ0KDIpLCBwaSkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2FtcGxlKDIwMDAwLCAxMCkpCmBgYAoKLS0tLQoKw4ljaGVsbGUgbG9nYXJpdGhtaXF1ZSBGVFcuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBzY2FsZV95X2xvZzEwKCkKYGBgCgoKIyMgTGVzIGFubm90YXRpb25zCgpWaWEgdW4gbWFwcGluZywgcGFyIGV4ZW1wbGUgcG91ciB1biBNRFMuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgbGFiZWwgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3RleHQoKQpgYGAKCi0tLS0KCkwnYW5ub3RhdGlvbiBtYW51ZWxsZSBlc3QgcG9zc2libGUsIMOgIGwnYW5jaWVubmUsIG1haXMgcGFzIGZvcmPDqW1lbnQgcmVjb21tYW5kw6llLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSkpICsgCiAgZ2VvbV9wb2ludCgpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAzLjUsIHkgPSAxMDAwMCwgbGFiZWwgPSAiSEVMTE8iKQpgYGAKCgojIyBEJ2F1dHJlcyB0eXBlcyBkZSBncmFwaGlxdWVzCgpDJ2VzdCBsYSBzZXVsZSBmb2lzIHF1ZSBub3VzIHZveW9ucyB1bmUgdHJhbnNmb3JtYXRpb24gc3RhdGlzdGlxdWUgZGFucyBjZXR0ZSBwcsOpc2VudGF0aW9uIChtYWxoZXVyZXVzZW1lbnQpLgoKQ2Ugc29udCBsZXMgZm9uY3Rpb25zIGNvbW1lbsOnYW50IHBhciBgc3RhdF9gLgoKUG91ciBwbHVzIGQnaW5mb3MsIHZvaXIgbGEgY2hlYXQgc2hlZXQuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyhwcmljZSkpICsKICBnZW9tX2FyZWEoc3RhdCA9ICJiaW4iKQpgYGAKCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHByaWNlKSkgKwogIGdlb21fZGVuc2l0eShrZXJuZWwgPSAiZ2F1c3NpYW4iKQpgYGAKCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHByaWNlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMzApCmBgYAoKLS0tLQoKQXZlYyB1bmUgdmFyaWFibGUgZGlzY3LDqHRlLCBjZXR0ZSBmb2lzLgoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoY29sb3IpKSArCiAgZ2VvbV9iYXIoKQpgYGAKCi0tLS0KClVuZSB2YXJpYWJsZSBkaXNjcsOodGUgZXQgdW5lIHZhcmlhYmxlIGNvbnRpbnVlLgoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNvbG9yLCB5ID0gcHJpY2UpKSArCiAgZ2VvbV9ib3hwbG90KCkKYGBgCgotLS0tCgpEZXV4IHZhcmlhYmxlcyBkaXNjcsOodGVzLgoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGN1dCwgeSA9IGNvbG9yKSkgKwogIGdlb21fY291bnQoKQpgYGAKCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyh4PXByaWNlLCBmaWxsPWN1dCkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oKQpgYGAKCi0tLS0KCkRpc3RyaWJ1dGlvbnMgYmktdmFyacOpZXMuCgpgYGB7cn0KZ2dwbG90KGRpYW1vbmRzLCBhZXMoY2FyYXQsIHByaWNlKSkgKwogIGdlb21fYmluMmQoYmlud2lkdGggPSBjKDAuMjUsIDUwMCkpCmBgYAoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtb25kcywgYWVzKGNhcmF0LCBwcmljZSkpICsKICBnZW9tX2hleCgpCmBgYAoKCgoKCiMjIExlcyB0aMOobWVzCgpMYSBmb25jdGlvbiBgZ2d0aXRsZWAgZXN0IHV0aWxpc8OpZSBwb3VyIGNob2lzaXIgdW4gdGl0cmUuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBnZ3RpdGxlKCJNb24gam9saSBncmFwaGlxdWUiKQpgYGAKCi0tLS0KCkV0LCBjbGFzc2lxdWUgcG91ciB1bmUgZm9pcywgYHhsYWJgIGV0IGB5bGFiYCBwb3VyIGNoYW5nZXIgbGVzIG5vbXMgZGVzIGF4ZXMuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICB4bGFiKCJNYSBqb2xpZSBhYnNjaXNzZSIpICsgCiAgeWxhYigiTWEgam9saWUgb3Jkb25uw6llIikKYGBgCgotLS0tCgpQb3VyIHZhcmllciBsZXMgdGjDqG1lcyA6IGB0aGVtZV9gLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSkpICsgCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV9idygpCmBgYAoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSkpICsgCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV9kYXJrKCkKYGBgCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCiMjIFNhdXZlciB1biBncmFwaGlxdWUKCmBgYHtyfQpnZ3NhdmUKYGBgCgotLS0tCgpQYXIgZXhlbXBsZSAKCmBnZ3NhdmUoInBsb3QucGRmIiwgd2lkdGggPSA3LCBoZWlnaHQgPSA3KWAKCiMjIEJpZW4gcHLDqXBhcmVyIHNlcyBkb25uw6llcwoKwqtUaWR5IGRhdGEgaXMgYSBzdGFuZGFyZCB3YXkgb2YgbWFwcGluZyB0aGUgbWVhbmluZyBvZiBhIGRhdGFzZXQgdG8gaXRzIHN0cnVjdHVyZS7CuyAKCsKrQSBkYXRhc2V0IGlzIG1lc3N5IG9yIHRpZHkgZGVwZW5kaW5nIG9uIGhvdyByb3dzLCBjb2x1bW5zIGFuZCB0YWJsZXMgYXJlIG1hdGNoZWQgdXAgd2l0aCBvYnNlcnZhdGlvbnMsIHZhcmlhYmxlcyBhbmQgdHlwZXMuwrsgCgotLS0tCgrCq0luIHRpZHkgZGF0YToKCjEuIEVhY2ggdmFyaWFibGUgZm9ybXMgYSBjb2x1bW4uCjIuIEVhY2ggb2JzZXJ2YXRpb24gZm9ybXMgYSByb3cuCjMuIEVhY2ggdHlwZSBvZiBvYnNlcnZhdGlvbmFsIHVuaXQgZm9ybXMgYSB0YWJsZS7CuwoKU291cmNlIDoKCmBgYHtyfQojIGxpYnJhcnkodGlkeXIpCiMgdmlnbmV0dGUoInRpZHktZGF0YSIpCmBgYAoKLS0tLQoKTWFyaWUtTG91aXNlIFRpbWNrZSBhIHByb3Bvc8OpIHN1ciBgam91cm5vY29kZS5jb21gIFt1bmUgdHLDqHMgYm9ubmUgcmVzc291cmNlIMOgIGNlIHN1amV0XShodHRwOi8vam91cm5vY29kZS5jb20vMjAxNi8wMy8wNS9yLXRpZHktZGF0YS8pLgoKRW4gcGFydGljdWxpZXIsIGVsbGUgcHLDqXNlbnRlIGwnZXhlbXBsZSBzdWl2YW50LCBleHRyYWl0IGQnZXhwbGljYXRpb25zIGQnSGFkbGV5IFdpY2toYW0uCgoKLS0tLQoKTGUgZm9ybWF0ICp0aWR5Kiwgb3UgKmxvbmcgZm9ybSosIGVzdCBmb3J0ZW1lbnQgcmVjb21tYW5kw6kgZGFucyBgZ2dwbG90MmAuCgoKCgoKCgoKCgoKCgo=